首先看代码:
//实验一
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相关变量,那么当前线程的工作内存副本中就不会去跟主存通信,及时的从主内存中刷新数据。
当然这些实验也有一些问题,我从网上也没有搜到相关的一些资料。如果有问题,欢迎留言,讨论,共同进步。