并发编程-Java内存模型:解决可见性与有序性问题

时间:2022-12-27 07:48:47

背景

我们知道导致cpu缓存导致了可见性问题,编译器优化带来了有序性问题。那么如果我们禁用了cpu缓存与编译器优化,就能够解决问题,但是性能就无法提升了。所以一个合理的方案,就是按照一定规范来禁用缓存和编译器优化,即在某些情况下禁用缓存与编译器优化。Java内存模型就是这样的一个规范,用来解决可见性与有序性问题

概念

java内存模型本质上就是规范了JVM如何按照规则禁用缓存和编译器优化,既面向应用开发人员,也面向jvm的实现。这些规范包括了volatile、synchronized和final三个关键字,以及六项Happens-Before规则。

volatile

volatile实质就是告诉编译器,对volatile修饰的变量,不能使用cpu缓存,必须从内存中读取或者写入。

Happens-Before规则,简称为HB

HB本质是一种可见性,即前面一个操作结果对后面的操作是可见的,换句话说就是后面的操作能够看见前面的操作结果。HB约束了编译器的优化行为,允许优化,但是优化后的结果要符合HB规则

同一线程顺序性规则

这条规则是指在一个线程中,按照程序顺序,前面的操作HB于后续的操作。

volatile变量规则

这条规则是指对一个 volatile 变量的写操作, HB于后续对这个变量的读操作。

传递性

这条规则是指A HB于 B,B HB于 C,那么A HB 于C。
根据以上三条格则继续看下面的例子

public class HappensBeforeDemo {
    int x = 0;
    volatile boolean v = false;
    public void write(){
        x = 42;
        v = true;
    }
    public void read(){
        if(v){
            //x为多少呢
        }
    }
}

当线程A执行完write方法,线程B执行read方法后,读到了v为true后,x是多少呢?首先在线程A中,根据同一线程顺序性规则,x=42 HB与v=true;再根据volatile变量规则,v=true的写操作HB于v的读操作;再根据传递性规则,x=42 HB于v的读操作,所以线程B中的值一定为42。在java并发包中的工具类,正是依靠这三条规则来实现可见性的

synchronized锁规则

这条规则指对一个锁的解锁操作HB于后续对这个锁的加锁加锁操作

线程start()规则

主线程A启动子线程B后,子线程B能够看到主线程在启动子线程之前的操作结果

线程join规则

在主线程A中等待子线程B完成(主线程A通过调用子线程B的join方法实现),当子线程完成后(主线程中的join方法返回),主线程能够看到子线程的操作结果