目录
一、问题抛出
当使用两个线程同时对一个变量自增5万次,预测结果是自增的和10万,但是代码正确的情况下,执行的结果随机且错误,这就是线程不安全造成的现象。
二、分析导致线程不安全的原因
1.线程的抢占式执行
线程的执行顺序并不是按照我们期望的顺序执行,这是线程不安全的主要原因,并且这个问题我们解决不了,完全由cpu随机调度。
2.多个线程改变了同一变量
多个线程修改不同变量不会出现线程不安全问题;多个线程读取同一个变量不会出现线程不安全问题;同一个线程读取或修改同一个变量不会出现线程不安全问题。
3.原子性
要么全都执行,要么全都不执行。
代码中的count++对应到cpu中有三条指令(读取、执行、写会)
由于cpu指令不是原子性的,因此三个指令都没有执行完就被cpu调度走了,导致其他的线程执行时没有读到上一个线程修改后的新值,最后结果出现错误。
4.内存可见性
JMM(Java Memory Model)Java内存模型
每个工作内存都是独立的,相互之间不能访问。
内存可见性就是使相互之间能够访问,这样当我们在某一个工作内存中修改值时,其他线程也能感觉到。一般情况下,当我们不对变量做任何处理时不能达到能存可见性的效果。
5.指令重排序
编译器会将我们的代码进行进行强制重排(JAVA层面会重排,JVMC++层面会重排,CPU执行指令阶段也会重排)
指令重排在单线程环境下没有影响,且必须建立在结果正确的情况下,操作逻辑互不影响。
总结
造成线程不安全的主要原因:
1.线程的抢占式执行
2.多个线程改变了同一变量
3.原子性
4.内存可见性
5.有序性
三、解决线程不安全的问题
1.线程的抢占式执行
CPU调度的方式,硬件层面无法解决
2.多个线程改变了同一变量
工作中需要并发编程提高效率,必须满足
3.原子性
指令是在CPU上执行的,CPU执行不是原子性,我们可以通过某些指令使他满足原子性
4.内存可见性
进程之间的内存是隔离的,而且有内存间通信的机制,那么线程之间也会有
5.有序性
通过JAVA中一些关键字使程序不进行指令重排序
通过上面的分析,我们可以通过改变3 4 5点来使线程安全,而3 4 5 恰好就是JMM的主要特性,我们可以在JAVA层面解决线程不安全问题。
通过加锁的方式来满足3 4 5的原子性。