«

Java内存模型-volatile可见性

晨曦 发布于 阅读:56 Java基础


Java内存模型:

        Java线程内存模型与CPU缓存模型类似,基于CPU缓存模型建立,Java线程内存模型是标准化的,用于屏蔽各种硬件和操作系统的内存访问差异。

主要分为三大模块: 线程、工作内存、主内存

Java内存模型数据原子操作:
read(读取) 从主内存读取数据
load(载入) 将主内存读取到的数据写入工作内存
use(使用) 从工作内存读取数据来计算
assign(赋值) 将计算好的值重新赋值到工作内存中
store(存储) 将工作内存数据写入主内存
write(写入) 将store过去的变量赋值给主内存中的变量
lock(锁定) 将主内存变量加锁,标识为线程独占状态
unlock(解锁) 将主内存变量解锁,解锁后其他线程可以锁定该变量

Volatile:
        volatile是Java虚拟机提供的最轻量级的同步机制。
        volatile保证可见性(所有线程都能看到共享的最新状态)与有序性(禁止指令重排序优化),但是不保证原子性,保证原子性需借助synchronized、Lock锁机制

Volatile如何保证内存可见性:

    read(读取)、load(载入)、use(使用)动作必须连续出现。
    assign(赋值)、store(存储)、write(写入)动作必须连续出现。

volatile关键字使变量的读、写具有了原子性。然而这种原子性仅限于变量(包括引用)

    ①基本类型的自增(如count++)等操作不是原子的。
    ②对象的任何非原子成员调用(包括成员变量和成员方法)不是原子的。

volatile内存可见性实现原理:

        底层实现主要通过汇编lock前缀指令(在汇编lock指令前后加上内存屏障),它会锁定这块内存区域的缓存并回写到主内存,此操作被称为”缓存锁定“,缓存一致性机制会阻止同时修改被两个以上的处理器缓存的内存区域数据。一个处理器的缓存回写到内存会导致其他处理器的缓存无效。

/**
* @Description:Java内存模型-可见性
* @Author:chenxi
* @Date:2020/3/21
**/
public class JMMVisibilityTest {

    private static volatile boolean initFlag = false;

    public static void writeInitFlag(){ 
        System.out.println("start write initFlag...");
        initFlag = true;
        System.out.println("end write initFlag...");
    }

    public static void loadInitFlag(){
        while (!initFlag) {
        }
        System.out.println("current thread " +         Thread.currentThread().getName() + " sniffing initFlag value change");
    }

    public static void main(String[] args) {
        Thread thread_1 = new Thread(()->{
            loadInitFlag();
        }, "thread-1");

        Thread thread_2 = new Thread(()->{
            writeInitFlag();
        }, "thread-2");

        thread_1.start();

        try {
            Thread.sleep(1000);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        thread_2.start();
    }
}

Java内存模型缓存不一致性底层机制:
Bus总线加锁(性能太低):
        cpu从主内存读取数据到高速缓存,会在总线对这个数据加锁,其他cpu没法去读取或写入这个数据,造成阻塞,直到这个cpu使用完数据释放锁之后其他cpu才能读取该数据。总线加锁使CPU性能发挥不出来。

Java代码执行底层汇编查看vm配置:

-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*JMMVisibilityTest.writeInitFlag

MESI缓存一致性协议:
        多个cpu从主内存读取同一个数据到各自的高速缓存,当其中某个cpu修改了缓存里的数据,该数据会立马同步到主内存,其他cpu通过总线嗅探机制可以感知到数据的变化从而将自己缓存里的数据失效,在下一轮指令周期从主内存中重新load数据。

MESI协议中状态:

M:被修改(Modified)

该缓存行只被缓存在该CPU的缓存中,并且是被修改过的(dirty),即与主内存中的数据不一致,该缓存行的内存需要在未来某个时间点(允许其他CPU读取主内存中相应数据之前)写回(write back)主内存中。    当被写回主内存后,该缓存行的状态会变为独享(Exclusive)状态

E:独享的(Exclusive) 该缓存行只被缓存在该CPU的缓存中,它是未被修改过的(clean),与主内存中数据一致,有其他CPU读取该内存时变成共享(Shared)状态。 当CPU修改该缓存行内容时,该缓存行状态变为被修改(Modified)状态
S:共享的(Shared) 该缓存可能被多个CPU缓存,并且各个缓存中的数据与主内存数据一致(clean)。 当有一个CPU修改该缓存行时,其他CPU中该缓存行内容被视为无效(Invalid)状态
I:无效的(Invalid) 该缓存无效(可能被其他CPU修改了该缓存行) 使用(use)时需重新从主内存中读取(read)数据

        注意:变量所属内存区域必须是在缓存行,不能超过缓存行大小(缓存行大小一般64字节,较大的为128字节),变量如果超过缓存行大小,缓存一致性协议无法工作,就会使用总线锁

Java