概要:5 Happens-before、6有序性
阻止(伪)编译器认为的无法“被代码本身”改变代码(变量/对象)进行优化。
JDK1.2 前,Java内存模型实现总是从主存(即共享内存)读取变量。当前内存模型,线程可以把变量保存本地内存(机器寄存器),不直接在主存中读写。线程主存中修改,另一个线程继续用寄存器中值(拷贝),造成数据的不一致。
解决:变量声明为 volatile,指示 JVM,这个变量不稳定,每次使用它到主存中读取。
二 volatile关键字的可见性
volatile 修饰的变量,强迫从主存中读写,不同线程看到变量同一个值,保证可见性。
isRunning变量没有加上volatile关键字时,死循环,isRunning被修改但是没有被写到主存中,线程本地内存中一直为true
while里加输出或sleep死循环也会停,不管是否volatile关键字。
JVM会尽力保证内存的可见性,只要CPU有时间,JVM会尽力去保证变量值的更新。
不同,volatile强制保证可见性。CPU占用,JVM不强制CPU去取最新值。加输出或sleep后,有时间去保证内存可见性
三 无法保证对变量原子性
自增操作(非原子操作)上不保证(个人感觉有问题)
运行结果:上面的“count=i;”是一个原子操作,大部分都是正确结果99,也有部分不是99的结果。
解决办法:
使用synchronized关键字加锁。(这只是一种方法,Lock和AtomicInteger原子类都可以)
四 synchronized关键字和volatile关键字比较
1.volatile轻量级,性能好;只用于变量;不会阻塞
synchronized可方法、代码块。会阻塞;1.6后效提升,实际用的多。
2.volatile解决可见性,不能保原子性。synchronized解决同步性,都能保证。
五、Happens-before
A Happens-before B:A对B可见。Happens-before原则:
1)顺序规则:线程中,按照控制流顺序,前面Happens-Before后续
2)volatile变量规则:volatile写操作Happens-Before于后续读操作(时间上)
3)传递性规则:A Happens-Before B 且 B Happens-Before C,那么A Happens-Before C
4)锁的规则:解锁Happens-Before于后续加锁。(时间上)
5)线程start()规则:主线程A启动B start(),start() Happens-Before B任意操作
6)线程join()规则:主线程A等待子线程B完成(主线程A调用子线程B的join()方法),当子线程B完成后,主线程能够看到子线程的操作(指的是对共享变量的操作)。
7)线程中断规则:对线程interrupt()方法的调用先行发生与被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()检测是否发生中断。
8)对象终结规则:一个对象的初始化的完成,也就是构造函数执行的结束一定Happens-Before它的finalize()方法。
ps:与时间先后顺序没关系,以Happens-Before原则为准
六、volatile解决有序性问题
依赖Happens-before前3条规则。A先于B,A对B可见
x(没有volatile)有没有可能等于0,因为编译优化带来指令重排,导致v = true 先于x = 42执行,reader()正好在两者之间执行,用volatile修饰后,再分析:
1、先看Happens-Before第二条(写先于读),赋值判断,writer()对v赋值,reader()判断v值true后,说明v写先读。
2、结合Happens-Before第一条,可以得出
x = 42 Happens-Before v = true
v = true Happens-Before if(v)(判断为true时)
if(v) Happens-Before System.out.println(x)
3、再结合Happens-Before第三条传递性, x取值只能是42。
x = 42 Happens-Before System.out.println(x)
https://blog.csdn.net/zcyt085/article/details/108309917