搞懂volatile(java线程和unix线程)(一)

        写在开头的地方,本文是笔者的理解,不一定正确,但属于是自己较为深入的学习所得,在此进行分享学习。

        话不多说,开搞, 一些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字段修饰,所以「感受到数值的改变」这句话不会被输出。

搞懂volatile(java线程和unix线程)(一)

实际情况也的确如此。

那么我们再来看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,同样的线程逻辑。

搞懂volatile(java线程和unix线程)(一)

但是却发现结果不同,这是为什么呢???

那么我们的第一个问题

问题一 为什么java线程修改全局变量是不可见的,而c++(unix)线程修改全局变量却是可见的。

我们先来回答后者,使用clion自带的lldb,我们debug可以看到反汇编

 搞懂volatile(java线程和unix线程)(一)

这句汇编很简单吧,简单的赋值操作,也就是说,是直接修改内存的值,所以c++线程修改全局变量是可见的。

那么我们再来看看,为什么java线程修改全局变量是不可见的,讲道理,java基于jvm运行,jvm是c++写的,也应该可见啊。其实用理论知识证明也很容易,

证明:因为java线程是运行在虚拟机栈中的,而虚拟机栈是不共享的。

           因为java线程是面向对象的形式出现的,且java的线程实现了自己的一套JMM模型。

           因为java虚拟机栈中存在栈帧,也就是在java语言进行a = 1时做的操作是如何的,通过jclasslib插件我们可以很清晰看到

搞懂volatile(java线程和unix线程)(一)

icounst_1: 将常量1压入操作数栈

putstatic: 将操作数栈中顶端第一个值弹出栈对a进行赋值

可以发现,这两条指令都是在虚拟机栈中进行的,而虚拟机栈又不是共享的,所以a的改变并不会同步到方法区的引用中去。

问题二 为什么java中全局变量加了volatile关键字,字段就有了「可见性」

这个就留到之后再讲吧,其实网上资料也挺全的了,内存屏障啊,happens-before原则之类的,但是大多都还是理论,我看看之后有空把源码翻出来整理整理吧。本文到此结束。 

后记:对于问题2,putstatic,getstatic这类字节码中在bytecodeparse解析中会有一个判断,is_volatile()的方法,有兴趣可以自己先看看。

 

上一篇:Java集合(没写完)


下一篇:Java的数据类型