Java的内存模型

JMM 即 Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、 CPU 指令优化等。

JMM 体现在以下几个方面

  • 原子性-保证指令不会收到线程上下文切换的影响
  • 可见性-保证指令不会收到cpu缓存影响
  • 有序性-保证指令不会收到cpu指令并行优化影响

可见性

static boolean run = true;

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(() -> {
        while (run) {
            //System.out.println(1);
        }
    });
    t.start();
    //睡眠1毫秒
    //sleep(1);
    //睡眠2毫秒
    sleep(2);
    run = false; // 线程t不会如预想的停下来
}

现象:

  1. sleep(2)主线程休眠2毫秒程序不会退出。

    初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存。

    因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中, 减少对主存中 run 的访问,提高效率。

    2毫 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量 的值,结果永远是旧值。

  2. sleep(1)主线程休眠1毫秒时候程序会退出。

    这种现象的产生是run的值还没有缓存到工作内存中。

  3. System.out.println(1);注释打开,不管休眠几毫秒程序都会退出。

    public void println(int x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

解决方案:

volatile static boolean run = true;

run变量增加volatile关键字修饰,保证可见性。

它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取 它的值,线程操作 volatile 变量都是直接操作主存。

volatile不能保证原子性

注意 synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是 synchronized 是属于重量级操作,性能相对更低 如果在前面示例的死循环中

有序性

JVM在不影响正确性的前提下,可以调整语句的执行顺序

static int i;
static int j;
//线程内执行赋值操作
i = ...; 
j = ...; 
//调换执行顺序,不影响结果
j = ...; 
i = ...; 

调换i,j的赋值顺序不会影响正确性。

这种特性称之为指令重排 ,但是在多线程下指令重排会影响最终的结果。

volatile 修饰的变量,可以禁用指令重排。常用于一个线程写,其他线程读取的情况。

测试习题

doInit() 方法仅被调用一次,下面的实现是否有问题,为什么?

public class TestVolatile {
    volatile boolean initialized = false;
    void init() {
        if (initialized) { 
            return;
        } 
        doInit();
        initialized = true;
    }
    private void doInit() {
        //doSomeThting
    }
}

多线程访问下,会存在问题。因为volatile只能保证变量的可见性,不能保证原子性。

当线程T1执行到if判断的时候initialized为false,还没来得及修改为true的时候,T2线程过来读到initialized仍然为false继续向下执行,doInit();

解决办法:

增加同步代码块,保证在读initialized和写initialized时候的原子性操作。

public class TestVolatile {
    volatile boolean initialized = false;
    void init() {
        synchronized(this){
            if (initialized) { 
                return;
            } 
            doInit();
            initialized = true;
        }
    }
    private void doInit() {
        //doSomeThting
    }
}
文章目录