Java中线程不安全的原因

首先什么是线程安全,线程安全就是多个线程修改同一个变量的时候,修改的结果需要和单线程修改的结果相同。如果修改的结果和预期不符,那就是线程不安全。导致线程不安全的原因有:

1.抢占式执行,这取决于cpu的调度,我们没有权力去改变。

cpu调度线程有可能是这样的,还有可能的情况并没有展示出来,我们没有权力去干涉cpu的调度,所以我们只有其他方法来保证线程安全。

2.多个线程修改同一个变量,注意,这里是同一个变量,通过上面这个图我们看的出来,当线程1在读一个数据的时候,线程2也可能读了原本的数据。在线程1保存之后,线程2还是修改的原来读的值,再进行保存,然后就会覆盖掉线程1修改的值,所以我们本来期望a这个变量是自增两次,但是到最后只自增了一次,与预期结果不符。多线程修改同一个变量这是我们可以改变的,只要我们不修改同一个变量,那每个线程自己干自己的事情,就不会导致线程不安全。

3.原子性,原子性就是这个操作不可以进行拆分,要么全部都执行,要么全部不执行,不能在被执行一半的时候被中断。上面举的例子a++这个操作就是可以拆分的操作,首先需要先读取a的值,再进行++操作,然后给a赋值,所以a++这个操作不是原子性操作,但是如果我们只是读一个或者单纯的赋值操作那就是原子性操作。上面的图就是因为a++这个操作不是原子性,然后线程间抢占式执行带来的结果。所以但是我们保证了操作的原子性,还是会发生线程不安全,例如

第一张图,在线程1保存之后,线程2再读,读取到的是保存之后的值,这没有问题,但是我们看第二张图,当线程1还没有保存的时候,线程2就读了a的值,读到的就是还没修改前的值,所以最后线程2修改的是线程1没有保存的值,a的值还是只增加了一次,这时候我们还需要保证内存可见性。内存可见性也是导致线程不安全的原因。

4.内存可见性,是指一个线程的操作对于另一个线程来说是可以见的,称为内存可见性,就像上面的图,线程1进行a++这个操作,然后线程2在线程1进行a++之后,读的值并不是a++之后的值,而是原本的值,线程1a++这个操作对于线程2是不可见的,所以他们不存在内存可见性。

说到内存可见性,我们需要了解一下JMM(Java内存模型)。

 每一个线程都有自己的一个工作内存,工作内存首先会将主内存的变量加载到自己的工作内存中之后变量的操作在工作内存中进行,然后在返回给主内存,工作内存一般都是高速缓存和寄存器,所以访问速度比访问内存要快的多。所以我们在进行一些操作的时候,就会发生偷懒的现象,例如:

 第一张图,当线程1进行读操作的时候,线程2已经保存了,这样子是没有什么问题,但是程序运行的时候并不是这样的,因为访问工作内存的速度比访问内存要快很多,所以编译器就偷懒了,直接给a自增好几次再返回给主内存,就会导致线程1不能及时得到修改后的值,导致线程不安全。所以我们还需要保证内存可见性,线程间的操作对于其他线程来说是需要可见的。

5.指令重排序,指令重排序是编译器自己优化的结果,我们写的代码执行的顺序,可能不是书写的顺序,编译器会自动优化这个代码的顺序,可能会导致线程不安全。指令重排序相比于原子性和可见性,遇到的场景并不多,所以我们简单了解即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值