java异步线程内存可见性实验
Posted PacosonSWJTU
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java异步线程内存可见性实验相关的知识,希望对你有一定的参考价值。
【README】
本文演示了内存可见性的场景,以及解决方法;
相关定义如下(转自java并发编程实战,一本好书,强烈推荐):
- 内存可见性:一个线程修改了对象状态后, 其他线程可以看到修改后的结果;
- 对象发布:使对象能够在当前作用域之外的代码中使用;
- 对象逸出:当某个不应该发布的对象被发布时;
【1】内存可见性问题例子
【1.1】测试用例
public class TestVisibility
public static void main(String[] args)
Robot robot = new Robot();
new Thread(
()->
try
// 睡眠模拟业务逻辑处理
TimeUnit.MILLISECONDS.sleep(1000);
catch (InterruptedException e)
robot.init();
).start();
System.out.println(robot.getAge());
int count = 0;
while(robot.getAge() == 0)
++count;
System.out.println("主线程【成功】读取子线程设置的age值");
/**
* @description 机器人
* @author xiao tang
* @date 2022/2/13
*/
class Robot
int age = 0;
public void init()
this.age = 10;
public int getAge()
return this.age;
打印结果:
0
while进入了死循环
【1.2】 分析
子线程睡眠一秒,使得主线程可以先从主存(内存)读取到age的值,并放入自己的缓存;
然后子线程再更新age的值并同步到主存;
由于主线程不从主存读取age,所以它使用的是age的失效值(脏数据);
【2】解决方法
1)方法1, 为 Robot.age 添加 volative 修饰符; 以保证age的内存可见性;
2)方法2,为 getAge() 方法 添加 synchronized 修饰符,以保证age的内存可见性;
3)方法3,while循环体,即++count下面添加一条 打印语句,如下:
while(robot.getAge() == 0)
++count;
System.out.println(count);
为什么添加 System.out.println(count); 后就可以解决内存可见性问题了?
因为 pringln 内部是 synchronized 代码块来实现的;如下:
public void println(int x)
synchronized (this)
print(x);
newLine();
即 方法3与方法2的原理一样,借助 synchronized 能够保证内存可见性的原理;
【3】补充
1,对于睡眠语句, TimeUnit.MILLISECONDS.sleep(1000); 我们可以测试出 内存可见性效果;
但是,如果把 1000 换为 1 或 10, 500, ...... 会产生不同的效果,这就类似于不同业务逻辑有着不同的耗时; 即 内存可见性问题会因为不同的耗时而表现出不同的结果;
这就很麻烦了,因为如果业务逻辑真正存在可见性问题,但可能没有测试出来,但生产上又存在发生可能性;
我们唯一需要做的就是,在多线程编程时,保证内存可见性,安全的发布共享对象;
【3.1】 happers-before
以下内容转自 :
关于Java内存可见性的探究实验遇到的意外和happens-before - 简书
happens-before字面翻译过来就是先行发生,A happens-before B 就是A先行发生于B?
不准确!在Java内存模型中,happens-before
应该翻译成:前一个操作的结果可以被后续的操作获取。讲白点就是前面一个操作把变量a赋值为1,那后面一个操作肯定能知道a已经变成了1。我们再来看看为什么需要这几条规则?
因为我们现在电脑都是多CPU,并且都有缓存,导致多线程直接的可见性问题。
所以为了解决多线程的可见性问题,就搞出了happens-before原则,让线程之间遵守这些原则。编译器还会优化我们的语句,所以等于是给了编译器优化的约束。不能让它优化的不知道东南西北了!。
1)关于 happens-before,有以下8条规则:
- 单线程Happens-Before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。
- 锁的Happens-Before原则:同一个锁的unlock操作happen-before此锁的lock操作。(本文中的 System.out.println中的 synchronized 就是java内置锁)
- volatile的Happens-Before原则:对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)。
- Happens-Before的传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
- 线程启动的Happens-Before原则:同一个线程的start方法happen-before此线程的其它方法。
- 线程中断的Happens-Before原则:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
- 线程终结的Happens-Before原则:线程中的所有操作都happen-before线程的终止检测。
- 对象创建的Happens-Before原则:一个对象的初始化完成先于他的finalize方法调用。
详细请看:https://segmentfault.com/a/1190000011458941
2)本文中锁的happens-before的作用:
由于happens-before原则,在获取锁时,主线程会使自己CPU的缓存失效,重新从主内存中读取变量的值。这样,子线程中的操作结果就会被主线程感知到了,从主内存中获取了最新的a值。
以上是关于java异步线程内存可见性实验的主要内容,如果未能解决你的问题,请参考以下文章