变量可见性及volatile关键字

目标:

1.了解什么是变量可见性问题

2.造成变量可见性问题原因

3.volatile关键字


前提说明:变量可见性问题发生在多线程,只有多线程才有变量可见性问题

1.什么是变量可见性问题

问题1:如何在多线程之间共享变量?使用全局变量:静态变量或者共享对象

问题2:一个变量在线程1中被改变值了,在线程2中能看到此变量的最新值吗?不一定

有如下代码,执行结果是什么呢?线程会停止循环打印出i的值吗?

import java.util.concurrent.TimeUnit;

public class TestVolatile {
    private static boolean isRunning = true;

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i=0;
                while (isRunning){
                    i++;
                }
                System.out.println(i);
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(2); //两秒之后
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isRunning = false;
        System.out.println("isRunning被设置为false了");
    }
}

结果线程并没有停止,i的值并没有打印出来。这就是并发中的变量可见性问题。即所见非所得。

怎么才能可见呢?有两种方式

1.加synchronized

2.加volatile关键字修饰

import java.util.concurrent.TimeUnit;

public class TestVolatile {
//    private static boolean isRunning = true;
    //第二种方式
    private static volatile boolean isRunning = true;

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i=0;
                while (isRunning){
                    i++;
                    //第一种方式
//                    synchronized (this){
//                        i++;
//                    }
                }
                System.out.println(i);
            }
        }).start();
        
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isRunning = false;
        System.out.println("isRunning被设置为false了");
    }
}

2.造成变量可见性问题原因

先了解下jmm(java内存模型)规范

1.共享变量必须放在主内存中

2.线程有自己的工作内存,线程只可操作自己的工作内存

3.线程要操作共享变量,需要从主内存读取到工作内存中,改变值后需要从工作内存同步到主内存中。

这就是线程安全问题根因,导致了变量可见性问题以及变量原子性问题,此处先讨论可见性问题。

变量可见性及volatile关键字_第1张图片如图显示了线程的流程。

问题1:变量在内存之间同步的时候被缓存了吗?

根据java内存模型我们可以知道变量修改完会同步到工作内存中。而且2秒肯定一定把变量同步到工作内存了。而内存之间的高速缓存并不是导致此问题的原因,这是更底层的开发者才会涉及的问题。java开发者可以不考虑各内存之间高速缓存问题。

问题2:使用共享变量前不会重新从主内存读取值吗?问题我们先放下。再了解一下指令重排

java编程语言的语义允许java编译器和微处理器进行执行优化,这些优化导致了与其交互的代码不再同步,从而导致看似矛盾的行为。

变量可见性及volatile关键字_第2张图片如图所示,这是一个简单的指令重排,导致结果跟代码执行结果不一致。

那么java中的指令重排什么时候会发生?。

这就不得不说到java的jit编译器(Just In Time Compiler)。java是解释性语言及编译性语言的混合体。

解释执行:即脚本,在执行时候,由语言的解释器将其一条条翻译成机器可识别的指令。

编译执行:将我们编写的程序直接编译成机器可以识别的指令码。即把整个批量编译。再分析一下代码执行。

变量可见性及volatile关键字_第3张图片java把java文件编译成类似字节码文件,并加载到jvm进程中执行

变量可见性及volatile关键字_第4张图片

jit此时就把循环这块进行了isRunning缓存及指令重排。如上图所示。这就导致他并不会重新从主内存读取isRunning的值,并且主内存中此变量值改变了也不会导致线程中的while循环停止。所以此处就导致了可见性问题。

3.volatile关键字

解决此问题可以使用volatile关键字,即上文中的代码中的第二种方式。第一种方式加synchronized解决原因先不讨论。

根据jmm中规定的happen before和同步规则,对某个volatile字段的写操作happends-before每个后续对该volatile字段的读操作。对volatile变量v的写入,与所有其它线程后续对v的读同步。所以volatile关键字满足一下功能:

1.禁止缓存;(volatile变量的访问控制符会加个ACC_VOLATILE,此处可以参考oracle官网相应描述)

2.对volatile变量相关的指令不做重排序。

到此时我们就基本明白了变量可见性问题的原因及解决方案。

 

之后会继续写一下java中的原子操作以及java变量的原子性问题。

你可能感兴趣的:(java)