写在开头的地方,本文是笔者的理解,不一定正确,但属于是自己较为深入的学习所得,在此进行分享学习。
话不多说,开搞, 一些volatile的基础知识我就不说了,我们先来看两类代码java和c++的
先上java的代码,非常简单
public class Test_1 { private static int a = 0; public static void main(String[] args) { new Thread(() -> { System.out.println("等待thread-2改变对a的数值"); while (0 == a) { } System.out.println("感受到数值的改变" + a); }, "thread-1").start(); new Thread(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("thread-2改变对a的数值"); a = 1; }, "thread-2").start(); } }
因为静态字段a没有被volatile字段修饰,所以「感受到数值的改变」这句话不会被输出。
实际情况也的确如此。
那么我们再来看c++的代码,会涉及到一些unix线程编程,但不关键
int a = 0; void* thread1_do(void* arg){ INFO_PRINT("等待thread-2改变对a的数值\n"); while (0 == a){} INFO_PRINT("感受到数值的改变\n"); return NULL; } void* thread2_do(void* arg){ INFO_PRINT("thread-2改变对a的数值\n"); a = 1; return NULL; } int main(){ pthread_t tid_1[1]; pthread_t tid_2[1]; pthread_attr_t attrs_1[1]; pthread_attr_t attrs_2[1]; pthread_attr_init(attrs_1); pthread_attr_setdetachstate(attrs_1,PTHREAD_CREATE_DETACHED); pthread_attr_init(attrs_2); pthread_attr_setdetachstate(attrs_2,PTHREAD_CREATE_DETACHED); pthread_create(tid_1, attrs_1, thread1_do, NULL); pthread_create(tid_2, attrs_2, thread2_do, NULL); pthread_attr_destroy(attrs_1); pthread_attr_destroy(attrs_2); sleep(1); }
会发现,同样是局部变量的a,同样的线程逻辑。
但是却发现结果不同,这是为什么呢???
那么我们的第一个问题
我们先来回答后者,使用clion自带的lldb,我们debug可以看到反汇编
这句汇编很简单吧,简单的赋值操作,也就是说,是直接修改内存的值,所以c++线程修改全局变量是可见的。
那么我们再来看看,为什么java线程修改全局变量是不可见的,讲道理,java基于jvm运行,jvm是c++写的,也应该可见啊。其实用理论知识证明也很容易,
证明:因为java线程是运行在虚拟机栈中的,而虚拟机栈是不共享的。
因为java线程是面向对象的形式出现的,且java的线程实现了自己的一套JMM模型。
因为java虚拟机栈中存在栈帧,也就是在java语言进行a = 1时做的操作是如何的,通过jclasslib插件我们可以很清晰看到
icounst_1: 将常量1压入操作数栈
putstatic: 将操作数栈中顶端第一个值弹出栈对a进行赋值
可以发现,这两条指令都是在虚拟机栈中进行的,而虚拟机栈又不是共享的,所以a的改变并不会同步到方法区的引用中去。
这个就留到之后再讲吧,其实网上资料也挺全的了,内存屏障啊,happens-before原则之类的,但是大多都还是理论,我看看之后有空把源码翻出来整理整理吧。本文到此结束。
后记:对于问题2,putstatic,getstatic这类字节码中在bytecodeparse解析中会有一个判断,is_volatile()的方法,有兴趣可以自己先看看。