java volatile 笔记

104 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第15天,点击查看活动详情

Java中有很多关键字,其中比较重要的有volatile。这个也是一些面试中比较常问的一个点。通常会和synchronized一同出现来比较它们的异同。

volatile主要是为了java多线程服务的。在java文档中的描述是这样的,Java程序语言允许线程去访问共享变量。一个线程理论上应该确保它独占使用这些变量,一般会使用一个锁来保障变量的访问。

但是加锁的机制通常以损失性能为代价。java提供了volatile关键字来解决这种问题。

当一个字段被volatile声明,那么JMM(java 内存模型)可以确保所有访问这个变量的线程,读取到的都是一致的值。

这是因为再没有使用volatile声明的时候,多线程读取变量时下图这样的过程

image.png

变量本身的值是保存在主线程中的,而线程会首先将变量读取到当前线程的工作内存中去,具体是CPU拷贝主内存中的变量值到cpu的缓存中,如果是多CPU的机器每个线程拷贝之后所缓存的位置也不同。

例如有个共享变量a = 1,线程一和线程二分别读取了变量a的值到自己的工作内存中。然后线程一修改了变量值为2,此时他会先更新当前工作内存中的变量a的值,然后在把这个值覆盖到主内存中的变量a上面,那么就造成,线程二修改a之后,线程一还是往自己的工作内存中读取a的值,就会读取到一个和线程二中不一致的a。

那么为什么需要怎么做呢?由于CPU从屋里内存中读写数据的速度远低于CPU的计算速度。所以为了提升性能特意开辟了一个CPU缓存,让线程优先读取这里的值,再刷新回主内存。

正因为如此,多线程环境下,会造成一个共享变量,不同线程之间各自优先操作了自己的工作内存,而没有及时刷新回主内存。某一个线程对变量做出了修改,别的线程由于读取的是自己线程的变量值,也无法可见到别的线程工作内存中的值。

volatile标记的字段,在线程读写时不会再往工作线程中进行,而是直接刷新主内存,也就是多个线程都会去主内存获取变量。

下面时一个例子

public class Product {
    static boolean flag = true;

    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("线程启动");
                while (flag) {

                }
                System.out.println("值被修改");
            }
        };
        thread.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = false;
        System.out.println("主线程修改值");
        System.out.println(flag);
    }
}

运行之后,尽管在主线程中flag的变量值已经修改成false了,但是在thread线程中还是最初读取到的值true,所以while循环并不会停止。

static volatile boolean flag = true;

为变量加上volatile,再次运行程序,可以看到thread在启动后很快输出了“值被修改”,同时也跳出了while循环。说明两个线程都是从主内存中读取的变量。这里例子也很好的说明了volatile具有可见性这一特点。