可见性验证:
//volatile可见性验证
public class volatileTestone {
public static void main(String[] args) {
MyData myData = new MyData();
//新建kjx线程先睡1s再 将num加了60,然后打印出来
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addTo60();
System.out.println("kjx线程:"+myData.number);
},"kjx").start();
//如果main线程检测不到number=60,将会一直循环
while (myData.number!=60){
}
//main线程打印出修改后的num
System.out.println("main线程:"+myData.number);
}
}
public class MyData{
int number = 0;
public void addTo60(){
this.number = 60;
}
}
volatileTestone 的打印结果:
可以看到number没有加volatile关键字时,main线程是不会发现在kjx线程中number已经被修改了。所以一直循环,程序无法结束
将number加上volatile关键字
public class MyData{
volatile int number = 0;
public void addTo60(){
this.number = 60;
}
}
执行结果:
可以看到number加上volatile关键字后,在kjx线程中被改变后,会通知main线程等其他的线程。体现出来volatile可见性的特性。
以上可以验证volatile的可见性。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
其实在验证的过程中也走了很多坑。列如system.out.println,sleep等等。下面记录一下。
由于我习惯用system.out.println输出在控制台观察结果。最开始在while循环里加了sout,结果在没加volatile的情况下,程序竟然停止了!显然system.out.println影响了可见性,也就是main线程已经获取到了kxj线程工作内存中的数据。
//如果main线程检测不到number=60,将会一直循环
while (myData.number!=60){
System.out.println("myData.number:"+myData.number);
}
点进println方法:
该方法是加锁的synchronized,于是查了下资料
JMM关于synchronized的两条规定:
1)线程解锁前,必须把共享变量的最新值刷新到主内存中
2)线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新获取最新的值
(注意:加锁与解锁需要是同一把锁)
synchronized具体过程是:
所以说number变量在这个过程中,已经将工作内存中的值同步到主内存中,main线程再次读取时即可见值已经变了。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
由于之前验证的时候想让while循环在number被改变后在运行,所以在main线程中加了sleep(其实没啥意义,但是却又给自己挖了个坑,给搞懵逼了)。
加完之后,kjx线程中数据是延迟1秒后变化的,所以main线程我就延迟了2s,结果!!!
//volatile可见性验证
public class volatileTestone {
public static void main(String[] args) {
MyData myData = new MyData();
//新建kjx线程先睡1s再 将num加了60,然后打印出来
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addTo60();
System.out.println("kjx线程:"+myData.number);
},"kjx").start();
//延迟两秒后再循环!!!
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果main线程检测不到number=60,将会一直循环
while (myData.number!=60){
System.out.println("myData.number:"+myData.number);
}
//main线程打印出修改后的num
System.out.println("main线程:"+myData.number);
}
}
又正常结束了。。。明明就只是加了个延迟,为啥kjx线程中的操作在main线程又可见了呢?
于是又去查资料
实际上,JVM对于现代的机器做了最大程度的优化,也就是说,最大程度的保障了线程和主存之间的及时的同步,也就是相当于虚拟机尽可能的帮我们加了个volatile,但是,当CPU被一直占用的时候,同步就会出现不及时,也就出现了后台线程一直不结束的情况。在线程中做一些耗时但不耗CPU的操作,也会结束的很快,因为这个时候CPU空闲了,JVM就有机会尽快的将主存和栈变量同步