如何解决线程安全问题(synchronized、原子性、产生线程不安全的原因,锁的特性,加锁的方式等等干货)

news2024/11/17 0:37:59

文章目录

  • 💐线程不安全的示例
  • 💐锁的特性
  • 💐产生线程不安全的原因:
  • 💐加锁的三种方式

💐线程不安全的示例

对于线程安全问题,这里用一个例子进行讲解👇:

我现在定义一个变量count, 使用两个线程对这个count进行自增,每一个线程中,count都自增10000, 那么,两个线程执行结束之后,最后的count应该是20000,下面验证一下:

public class Demo1 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            for(int i = 0; i < 10000; i++) {
                //count自增10000次
                count++;
            }
        });
        Thread thread2 = new Thread(() -> {
            for(int i = 0; i < 10000; i++){
                //count自增10000次
                count++;
            }
        });
        
        thread1.start();
        thread2.start();
        //让主线程阻塞等待
        thread1.join();
        thread2.join();
        //两个线程执行完毕后,打印最后结果
        System.out.println("count:"+count);
    }
}

第一次打印:

在这里插入图片描述

第二次打印:

在这里插入图片描述

第三次打印:

在这里插入图片描述

通过以上代码的得到,最后的结果与我们预期的结果完全不一样,而且,每次运行都会产生不一样的结果;以上代码如果放在一个线程中去运行,那是没有任何问题的,放在多线程中就出现了问题,以上情况就是一个非常典型的线程安全问题;

那么我们修改一下代码:

将thread1的join放在thread1的start后面,最后运行的结果就是对的!!!

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这个 “同时执行” 是很关键的,既然同时执行时会产生线程不安全问题,那么有没有办法解决这样的问题呢?答案是:有的

产生问题的愿意:

首先,count++ 这个操作,站在CPU的角度上是由三步操作完成的:

  1. load操作,把数据从内存中拿出来放到寄存器中

  2. add操作,把count的值进行+1

  3. save操作,把寄存器中的值放到内存中

以两个线程为例,因为线程是随机调度执行的,所以这三步操作就有以下的操作顺序:

在这里插入图片描述

上述,第一和第二种情况产生的结果就是正确的,下面通过画图演示一下以上这三步在CPU核心上的操作步骤,观察以下count是如何完成自增操作的;

假设,thread1 和 thread2这两个线程是在两个CPU核心上执行的,当然,在一个CPU核心上也是没有问题的,因为会有对应的上下文进行记录,为了好理解,下面我们假设在两个CPU核心上:

在这里插入图片描述

交叉执行的情况就会产生bug👇

在这里插入图片描述

由于在这20000次中,我们不知道有多少次是按照正确的方式自增的,又有多少次按照错误的方式自增的,所以最后就会产生一个小于20000的随机值;对于上述情况,就可以使用 **“加锁操作“**👇

💐锁的特性

在Java中,对线程加锁主要就是使用 synchronized

synchronized在使用时,需要修饰一个代码块,例如以下:

在这里插入图片描述

对上述自增例子的代码进行加锁:

public class Demo1 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();//创建一个对象作为锁对象
        Thread thread1 = new Thread(() -> {
            synchronized (obj) {
                //对线程进行加锁
                for(int i = 0; i < 10000; i++) {
                    count++;
                }
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (obj) {
                for(int i = 0; i < 10000; i++){
                    //count自增10000次
                    count++;
                }
            }
        });
		//此时两个线程同时执行,就会同时获取锁对象,就会产生竞争
        thread1.start();
        thread2.start();
        //两个线程执行完毕后,打印最后结果
        System.out.println("count:"+count);
    }
}

在这里插入图片描述

锁的特性主要分为两点:

(1)互斥特性

  • 进入synchronized修饰的代码块,相当于加锁
  • 退出synchronized修饰的代码块,相当于解锁

一个线程在加锁情况下,另一个线程也想要进行加锁,这样就会产生“锁冲突/锁竞争”,获得锁的线程就会继续执行,没有获得锁的线程就会阻塞等待,直到得到锁的线程释放锁为止;

如何理解阻塞等待?

例如这样一个场景,我们在排队上厕所时,当张三进入厕所后,就会将们锁住,这时候,李四和王五就只能在原地等待,等到张三出来以后,李四和王五才能去上厕所;

注意:

1.张三出厕所以后,李四和王五并不会按照顺序上厕所,而是会进行一个抢厕所,这就相当于,虽然现在有多个线程,但是在获取锁时,并不会按照先来后到的顺序获取,而是会进行争抢

2.上一个线程释放锁之后,下一个线程并不是直接就可以获取锁,而是需要操作系统进行一个“唤醒”的过程,这个过程是操作系统线程调度的工作部分

(2)刷新内存

synchronized的工作过程:

1.尝试获得锁

2.从主内存拷贝变量最新内容到工作内存

3.执行代码

4.将更改后的变量刷新到主内存中

5.释放锁

所以,synchronized也可以保证内存可见性

💐产生线程不安全的原因:

1.线程的调度顺序是随机的,是抢占式执行的

2.两个线程针同一个变量进行修改

1.两个线程针对两个不同的变量进行修改

2.一个线程针对一个变量进行修改

3.两个线程针对同一个线程进行读取

以上改正后的三点就不会发生不安全问题;

3.修改操作不是原子的

什么是原子性:就像最开始讲的 load,add,save 指令,它们是三个指令,需要一个一个执行,所以它们不是原子的,如果它们三个指令发生的动作能够一个指令就完成,或者说在它们三个指令执行的过程中,不会被其他的指令进行穿插,这就是原子的。

4.内存可见性问题

5.指令重排序问题

那么,如何将不安全改成安全的呢?

首先,第一条原因是没办法修改的,因为线程的抢占式执行是由操作系统决定的,我们是无法修改的,第二条原因的话,针对有些代码,可能可以经过代码的调整来避免,但是,有些代码可能无法调整,就比如我们写的这个,我们的目的就是让count在两个线程中进行自增,所以第二条原因需要针对业务的情况,第三条原因就可以进行修改,将不是原子的操作改为原子的,通过加锁就可以解决上述非原子性问题,内存可见性和指令重排序问题也可以通过 Volaite 来解决。

💐加锁的三种方式

1.synchronozed修饰代码块👇

  Object obj = new Object();
        Thread thread1 = new Thread(() -> {
            synchronized (obj) {
                //对代码进行加锁
                for(int i = 0; i < 10000; i++) {
                    count++;
                }
            }
        });

**2.修饰实例方法👇:**哪个对象调用increase,哪个就是 “锁对象”

class Counter{
    public static int count;
    //谁调用这个方法,谁就是锁对象
    //写法一:
    synchronized public void increase() {
        count++;
    }
    //写法二:
    public void increse() {
        synchronized(this) {
            count++;
        }
    }
}
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
       Counter counter = new Counter();
        
       Thread thread1 = new Thread(() -> {
           for(int i = 0; i < 10000; i++) {
               counter.increase();
           }
       });

        thread1.start();
        System.out.println(Counter.count);
    }
}

在这里插入图片描述

3.修饰静态方法👇

如果是修饰静态方法,相当于是对“类对象”进行了加锁;

class Counter{
    public static int count;
    //写法一:
    synchronized public static void increase1() {
        count++;
    }
	//写法二
    public static void increase2() {
        //对类对象加锁
        synchronized (Counter.class) {
            count++;
        }
    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1485701.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络安全是什么

网络安全是一种保护计算机、服务器、移动设备、电子系统、网络和数据免受恶意攻击的技术&#xff0c;这种技术也称为信息技术安全或电子信息安全。该术语适用于从业务到移动计算的各种环境&#xff0c;可以分为几个常见类别: 网络安全是一种保护计算机网络免受入侵者&#xff0…

mysql5.7配置主从

原理&#xff1a; MySQL主从复制的工作原理如下:1. 主服务器产生Binlog日志当主服务器的数据库发生数据修改操作时,如INSERT、UPDATE、DELETE语句执行,主服务器会记录这些操作的日志信息到二进制日志文件中。2. 从服务器读取Binlog日志 从服务器会向主服务器发送请求,主服务器把…

JVM(6)

JMM JVM定义了一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果.在此之前,C/C直接使用物理硬件和操作系统的内存模型,因此,会由于不同平台下的内存模型差异,有可能导致程序在一套平台上并发完全正常,而在另…

Unity曲柄滑块四杆机构运动计算

一、运动效果 二、机构的介绍 曲柄长度&#xff1a;a&#xff0c;线段AB长度 连杆长度&#xff1a;b&#xff0c;线段BC长度 偏心距离&#xff1a;e&#xff0c;滑块轨迹与曲柄中心点A的垂直距离 三、已知点A点B和e的值&#xff0c;计算C点的位置 1、计算s的值 var h math.…

绝望的C#:Task是个良好的设计吗?

我对高级语言的异步机制总感到理解起来比较困难。明确的创建任务、等待任务不好吗&#xff1f; 执行异步调用的方法返回一个Task是很正常的&#xff0c;很容易理解&#xff0c;但是Task.Result为什么导致阻塞呢&#xff1f; 一个属性&#xff0c;而不是一个方法&#xff0c;竟然…

Python算法题集_单词搜索

Python算法题集_单词搜索 题22&#xff1a;单词搜索1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【原始矩阵状态回溯】2) 改进版一【字典检测原始矩阵状态回溯】3) 改进版二【矩阵状态回溯】 4. 最优算法5. 相关资源 本文为Python算法题集之一…

鸿蒙开发案例:进京赶考(5)完结

系列文章目录 鸿蒙开发案例&#xff1a;进京赶考&#xff08;1&#xff09; 鸿蒙开发案例&#xff1a;进京赶考&#xff08;2&#xff09; 鸿蒙开发案例&#xff1a;进京赶考&#xff08;3&#xff09; 鸿蒙开发案例&#xff1a;进京赶考&#xff08;4&#xff09; 鸿蒙开…

ifcplusplus 示例 函数中英文 对照分析以及流程图

有需求&#xff0c;需要分析 ifc c渲染&#xff0c;分析完&#xff0c;有 230个函数&#xff0c;才能完成一个加载&#xff0c;3d加载真的是大工程&#xff01; 示例代码流程图 函数中英文对照表&#xff0c;方便 日后开发&#xff0c;整理思路顺畅&#xff01;&#xff01;&am…

KubeSphere平台安装系列之三【Linux多节点部署KubeSphere】(3/3)

**《KubeSphere平台安装系列》** 【Kubernetes上安装KubeSphere&#xff08;亲测–实操完整版&#xff09;】&#xff08;1/3&#xff09; 【Linux单节点部署KubeSphere】&#xff08;2/3&#xff09; 【Linux多节点部署KubeSphere】&#xff08;3/3&#xff09; **《KubeS…

个人项目介绍2:地球卫星篇

项目需求&#xff1a; 在项目中显示三维地球及主要城市标注&#xff0c;接收服务端发来的实施卫星数据&#xff0c;显示卫星姿态角&#xff0c;陀螺角&#xff0c;飞轮等数据&#xff1b;可自定义模拟产生更多卫星轨迹&#xff1b;可模拟显示卫星躲避陨石动画&#xff1b;可展…

【文献管理】zotero插件4——获取知网pdf、中文文献识别与目录生成

文章目录 zotero获取知网PDFzotero——中文文献识别&#xff08;茉莉花插件&#xff09;学位论文目录生成 zotero获取知网PDF zotero——中文文献识别&#xff08;茉莉花插件&#xff09; 为下载的学位论文添加目录中文文献识别&#xff1a;jasminum 下载pdflabs下载茉莉花插…

标准库中的String类 String(C++)【3】

文章目录 String常用的接口&#xff08;黑框标记的是常用接口&#xff09;数据访问operator:at:back:front: 数据修改push_back:append:operator:assigen:insert:erase:replace:注意事项 String常用的接口&#xff08;黑框标记的是常用接口&#xff09; 数据访问 operator: 返…

智能物联时代下RFID技术在汽车零部件智能制造中的引领作用

RFID&#xff08;Radio Frequency Identification&#xff0c;射频识别&#xff09;技术在汽车零部件加工中有广泛的应用&#xff0c;其工作原理是通过无线电频率进行自动识别。在汽车零部件加工中&#xff0c;RFID技术可以发挥重要作用&#xff0c;提高生产效率、降低成本和减…

【5G 接口协议】GTP-U协议介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

【虚拟机安装centos7后找不到网卡问题】

最近开始学习linux&#xff0c;看着传智播客的教学视频学习&#xff0c;里面老师用的是centos6.5&#xff0c;我这边装的是centos7最新版的 结果到了网络配置的这一节&#xff0c;卡了我好久。 我在centos一直找不到我的网卡eth0&#xff0c;只有一个回环网口&#xff0c;在/…

集成2.5G/5G/10G高速率网络变压器的RJ45网口连接器产品特点介绍

Hqst华轩盛(石门盈盛)电子导读&#xff1a;集成2.5G/5G/10G高速率网络变压器的RJ45网口连接器产品特点介绍&#xff1a; 第一、 高速率&#xff1a;支持高达2.5Gbps、5Gbps和10Gbps的传输速率&#xff0c;能够满足高带宽的网络应用需求。 第二、 集成2.5G/5G/10G高速率网…

编写dockerfile挂载卷、数据容器卷

编写dockerfile挂载卷 编写dockerfile文件 [rootwq docker-test-volume]# vim dockerfile1 [rootwq docker-test-volume]# cat dockerfile1 FROM centosVOLUME ["volume01","volume02"]CMD echo "------end------" CMD /bin/bash [rootwq dock…

Spring框架相关问题

Spring框架相关问题 一、Spring容器中的Bean是线程安全的吗&#xff1f;二、如何保证Spring容器中的Bean是线程安全的呢&#xff1f;三、什么情况下会触发Spring事务回滚&#xff1f;四、如果事务方法抛出IOException&#xff0c;是否会触发Spring事务回滚&#xff1f;五、什么…

在线简历制作网站免费推荐收藏备用

今天给大家推荐一个很实用的网站&#xff0c;对于要毕业的同学或者说跳槽的朋友比较有作用&#xff0c;对&#xff0c;就是一个免费方便的在线简历制作网站。其实免费的简历制作网站很多&#xff0c;但好用的良心的其实并不多&#xff0c;今天要推荐的这个虽然模板不算多&#…

1.2 在卷积神经网络中,如何计算各层感受野的大小

1.2 在卷积神经网络中&#xff0c;如何计算各层感受野的大小 分析与解答&#xff1a; 在卷积神经网络中&#xff0c;由于卷积的局部连接性&#xff0c;输出特征图上的每个节点的取值&#xff0c;是由卷积核在输入特征图对应位置的局部区域内进行卷积而得到的&#xff0c;因此这…