[java]线程安全问题

线程安全问题产生有五个产生原因:

(1)线程的随机调度和抢占式执行,就是这个机制使得线程安全问题产生;

(2)代码结构,多个线程对同一个变量进行修改

(3)原子性,修改操作的是可拆分的,导致脏读问题

(4)内存可见性问题(一个线程读一个线程写)

(5)指令重排序

我们接下来针对每种线程安全的产生原因,做出对应的应对方案

一 、线程的随机调度和抢占式执行

这是线程的基本特性,也是线程安全出现的根本原因,在java代码的编写层面,我们对这个原因无能为力。

二、代码结构,多个线程对同一变量进行修改

对于这个原因,在某些情况下,我们可以通过调整代码的结构,先让两个线程修改不同变量,最后将对不同变量的修改汇总到一个变量,也能完成任务,但是这需要特定的代码需求,不是所有的代码都能通过修改代码结构来避免线程安全问题;

三、原子性,修改操作可以拆分

修改操作可以被拆分是如何导致线程安全问题的呢,以count++为例,一++被分为三个阶段,load,add,save,loda就是从内存中将这个数据取得,add就是对这个数据在cpu中进行修改,save就是将修改后的数据保存回内存中,当多个线程同时对count进行修改时,由于修改操作是可以拆分的,所以线程完全有可能在执行到load或者add之后,CPU就跑去执行其他线程了,其他线程此时也会修改count,他load到的count和已经停在load或add操作的线程是一样的,当两个线程执行完两次++后,count最后只自增了一次;

实际的情况往往比上述过程更加复杂,很有可能count已经被自增了几百次了,此时又执行到了子层几百次之前load到count的线程,count一下就变回了自增几百次之前的的数值

只要我们让修改方法具有原子性,不可以被拆分,也能避免线程安全问题,javaz中实现"原子性"的方法就是加锁(synchronized 修饰),这个方法虽然并没有让修改方法变得不可拆分(执行修改操作时,线程也能进行切换),但是他让修改方法在被一个线程使用的时候,不能被其他线程使用,达到了和原子性一样的效果(这里加锁并没有真正意义上实现原子性,但是达成的效果和原子性是一样的)

四、内存可见性问题

我们先好好解释什么是内存可见性问题:

java编译器在运行代码时有时会对代码的执行进行优化,比如一个大量的循环的读取同一个变量线程代码,正常情况下,每次读取都要在内存中去取得该变量的值,但是编译器在判断这个变量每次都只是被读取,并没有被修改后,它就做了一个大胆的决定但是java编译器优化后,它只有第一次读取去内存中读,第一次读取完之后,他将读取后的值存放在cpu内部,以后每次读取都从cpu内部读取,(因为从cpu读取比从内存读取的速度快得多,所以这提高了代码的运行效率)

在单线程的情况下,上面的优化确实是没问题的,但是在多线程情况下,这个代码就回产生内存可见性问题

考虑以下情况:一个线程循环读取某个数据,判断这个数据是否等于某个值,另一个线程会修改这个数据来控制第一个线程的运行,当另一线程修改这个数据后,第一个线程读取不到被修改后的值,因为编译器将他优化为只能读取cpu中的数据,第一个线程无法读取到内存中被修改后的数据,此时就产生了内存可见性的问题

解决办法:

让编译器知道,读取的这个数据是易变的,是不能被优化的,就在这个数据前面加上volatile关键字,这个关键字加上之后,编译器就不会再针对这个变量进行优化

五、指令重排序

指令重排序也和编译器的优化有关

在创建一个变量,new object()时,执行的指令有三步:

(1)申请一个空间

(2)向这个空间装入实例的数据

(3)将这个空间的地址赋值给引用

编译器优化后,将123的执行顺序,改成了132,在单线程的情况下,这个优化也是没毛病的,在多线程时,又会出现问题,如果实例化的时候,执行完13后,cpu突然跑掉了,此时引用已经有了实际的空间地址,如果其他线程使用这个对象,将会得到错误的结果,这就是指令重排序导致的线程安全问题(这个线程安全在单例模式的懒汉模式下有应用)

解决方法也很简单,也是加上volatile关键子修饰这个变量

我们可以总结出volatile的两条作用:

(1)保证内存可见性:让一个线程等读取到另一个线程对内存数据的修改

(2)保证有序性:禁止指令重排序

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值