volatile标识字段会造成:当前任务线程的内存副本中的所有数据从主内存更新最新数据

首先看代码:

//实验一
public class Test1 {
    public volatile   int i = 0;
    public  int flag = 1;

    public static void main(String[] args) throws InterruptedException {
        Test1 test1 = new Test1();
        Thread a = new Thread(()->{
            try {
                Thread.sleep(1000);
                test1.flag = 0;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });
        Thread b = new Thread(()->{
            while (test1.flag!=0){
                test1.i++;
            }
        });
        a.start();
        b.start();
        a.join();
        b.join();
        System.out.println("end ,i="+test1.i);
    }
}

实验一打印输出:

end ,i=165243757

Process finished with exit code 0

然后去除掉int i 前面的volatile 字段

//实验二
public class Test1 {
    public int i = 0;
    public  int flag = 1;

    public static void main(String[] args) throws InterruptedException {
        Test1 test1 = new Test1();
        Thread a = new Thread(()->{
            try {
                Thread.sleep(1000);
                test1.flag = 0;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });
        Thread b = new Thread(()->{
            while (test1.flag!=0){
                test1.i++;
            }
        });
        a.start();
        b.start();
        a.join();
        b.join();
        System.out.println("end ,i="+test1.i);
    }
}

线程b 直接死循环,没有输出了。

我们都知道用volatile 标记的字段会有线程间可见性。如果没有标记这个字段的值,在两个线程之间是不可见的。 如果 volatile 标记在 flag前面。保证了线程a跟线程b之间flag 的可见性。实验一,可以正常输出我们能理解。但是,为什么标记在变量i前面,也能带来flag 的线程之间的内存可见呢?

个人猜测是不是JAVA 的内存模型JMM,在模仿cpu 的缓存行的机制带来的,让同一个缓存行一起刷新。因为volatile int i 跟 flag 在同一个缓存行,所以在刷新i 到主存 跟 b线程工作内存的时候,也把flag 刷进去了呢?
带着这个疑问我做了一下实验?

//实验三
public class Test1 {
    public volatile  int i = 0;
    long a1,a2,a3,a4,a5,a6,a7,a8,a9=0;
    public  int flag = 1;

    public static void main(String[] args) throws InterruptedException {
        Test1 test1 = new Test1();
        Thread a = new Thread(()->{
            try {
                Thread.sleep(1000);
                test1.flag = 0;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });
        Thread b = new Thread(()->{
            while (test1.flag!=0){
                test1.i++;
            }
        });
        a.start();
        b.start();
        a.join();
        b.join();
        System.out.println("end ,i="+test1.i);
    }
}

实验三添加了9个long 类型的数据,超过了缓存的行的大小,按猜测锁想实验三应该会有b线程死循环,造成没有输出。然而事实并非如此。
实验室三输出:

end ,i=163084240

Process finished with exit code 0

继续猜测,难道是JMM模型的缓存行超过了64个字节了?那我们继续加大

//实验四
public class Test1 {
    public volatile  int i = 0;
    long[] a1 = new long[10000000];
    public  int flag = 1;

    public static void main(String[] args) throws InterruptedException {
        Test1 test1 = new Test1();
        Thread a = new Thread(()->{
            try {
                Thread.sleep(100);
                test1.flag = 0;
                for (int i = 0; i < 10000000; i++) {
                    test1.a1[i] = i;
                }

            } catch (Exception e) {
                e.printStackTrace();
            }

        });
        Thread b = new Thread(()->{
            while (test1.flag!=0){
                test1.i++;
            }
        });
        a.start();
        b.start();
        a.join();
        b.join();
        System.out.println("end ,i="+test1.i);
    }
}

然而,实验结果依然是能获得输出

end ,i=15935534

Process finished with exit code 0

通过上述的实验我们得出,volatile字段加上之后 JMM 模型中各个线程模型并没有像cpu的内存模型那样有一个缓存行的概念。但是我们的疑问仍然存在,为什么volatile i 字段会影响相同 同一个类里面的没有被volatile标记的flag呢??

继续猜想,是不是只要JMM线程里面加载了一个volatile字段,会把整块工作线程的所有内存都保证了可见性?由此设计了实验如下

//实验五:
public class Test1 {
    public  int i = 0;
    public  int flag = 1;
public static void main(String[] args) throws InterruptedException {
        Test1 test1 = new Test1();
        Test3 test3 = new Test3();

        Thread a = new Thread(()->{
            try {
                Thread.sleep(100);
                test1.flag = 0;
            } catch (Exception e) {
                e.printStackTrace();
            }

        });
        Thread b = new Thread(()->{
            while (test1.flag!=0){
                test3.i++;
            }
        });
        a.start();
        b.start();
        a.join();
        b.join();
        System.out.println("end ,i="+test3.i);
    }
}

public class Test3 {
    public volatile  int i = 0;
}

end ,i=16666048

Process finished with exit code 0

实验竟然也是有结果的 ,我们用了class Test1 跟 Class Test2 ,被volatile标记的只有test2 中的 i,但是神奇的是 a线程中 test1.flag 的变量改变也通知到了b线程中。

由此我们可以得出一个简单的结论,当线程中有volatile变量加载的时候,JAVA任务线程工作内存副本,会从主存中刷新当前任务线程中曾经加载过的所有内存数据,来更新工作线程的副本。如果线程没有加载过任何volatile相关变量,那么当前线程的工作内存副本中就不会去跟主存通信,及时的从主内存中刷新数据。

当然这些实验也有一些问题,我从网上也没有搜到相关的一些资料。如果有问题,欢迎留言,讨论,共同进步。

你可能感兴趣的:(多线程高并发学习笔记)