java异步线程内存可见性实验

Posted PacosonSWJTU

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java异步线程内存可见性实验相关的知识,希望对你有一定的参考价值。

【README】

本文演示了内存可见性的场景,以及解决方法;

相关定义如下(转自java并发编程实战,一本好书,强烈推荐):

  1. 内存可见性一个线程修改了对象状态后, 其他线程可以看到修改后的结果;
  2. 对象发布:使对象能够在当前作用域之外的代码中使用; 
  3. 对象逸出:当某个不应该发布的对象被发布时;

【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条规则:

  1. 单线程Happens-Before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。
  2. 锁的Happens-Before原则:同一个锁的unlock操作happen-before此锁的lock操作。(本文中的 System.out.println中的 synchronized 就是java内置锁
  3. volatile的Happens-Before原则:对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)。
  4. Happens-Before的传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
  5. 线程启动的Happens-Before原则:同一个线程的start方法happen-before此线程的其它方法。
  6. 线程中断的Happens-Before原则:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
  7. 线程终结的Happens-Before原则:线程中的所有操作都happen-before线程的终止检测。
  8. 对象创建的Happens-Before原则:一个对象的初始化完成先于他的finalize方法调用。

详细请看:https://segmentfault.com/a/1190000011458941

2)本文中锁的happens-before的作用:

由于happens-before原则,在获取锁时,主线程会使自己CPU的缓存失效,重新从主内存中读取变量的值。这样,子线程中的操作结果就会被主线程感知到了,从主内存中获取了最新的a值。

以上是关于java异步线程内存可见性实验的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程之内存可见性

volatile为啥不能保证原子性

Java 内存模型

线程安全—可见性和有序性

多线程—— 内存可见性

Java多线程安全可见性和有序性之Volatile