锁的内存语义本质上可以说是对共享变量的更新,能及时让其他线程观察到;并且通过内存屏障,组织编译器或处理器指令重排序,导致多线程下不一致的现象。
1. volatile内存语义
见上一篇文章。
2. 锁的内存语义
(1)锁的释放和获取的内存语义
当线程释放锁时,JMM会将本地内存中的共享变量同步到主内存中;
当线程获取锁时,JMM会将该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。
(2)锁内存语义的实现
ReentrantLock的实现依赖于AbstractQueuedSynchronizer(AQS),AQS使用一个整形的volatile变量(state)来维护同步状态。这个volatile变量是ReentrantLock实现的关键。
编译器不会对volatile读与其后的任意内存操作重排序,不会对volatile写与其前的任意操作重排序。CAS同时具有volatile读和写的内存语义,编译器不会对CAS前和后的任意内存操作重排序,其是通过底层处理器缓存锁定实现原子性的。
CAS同时具有volatile读和写的内存语义,故Java线程之间的通信存在4种方式:
① A线程写volatile变量,随后B线程读该变量;
② A线程写volatile变量,随后B线程使用CAS更新该变量;
③ A线程使用CAS更新volatile变量,随后B线程使用CAS更新该变量;
④ A线程使用CAS更新volatile变量,随后B线程读该变量。
concurrent包的实现,通用的实现模式:
① 声明共享变量为volatile
② 使用CAS的原子条件更新来实现线程之间的同步
③ 配合以volatile的读/写,及CAS所具有的volatile读和写的内存语义来实现线程间的通信。
AQS、非阻塞数据结构和原子变量类,都是使用该模式来实现的。(该图摘自java concurrent包的实现原理)
3. final内存语义
(1)final重排序规则
① 在类实例化时,构造函数中对一个final域的写入,与后边对该类对象的引用之间不能重排序。防止引用到未初始化完全的对象
首先对象是共享对象如通过static修饰的类实例。JMM禁止编译器将final域的写重排序到构造函数之外;编译器会在final域写入之后,构造函数返回之前插入StoreStore屏障。
对于普通变量的赋值可能重排序到构造函数之外,此时另一个线程通过该对象访问普通变量时,可能还没有赋值。(具体实例参看《Java并发编程的艺术》)
② 初次读一个包含final域的对象,与初次读这个final域之间不能重排序。防止对象引用提前读,而对象还未初始化完全
在读对象引用和对象final域时,JMM在两者之间插入LoadLoad屏障,禁止读final域重排序到对象引用前边,普通变量可能存在这种情况。
③ 对final域是数组或对象等引用类型的情况,有如下约束:在构造函数内对一个final引用的对象的成员域的写入,与随后对该类对象的引用之间不能重排序。
即final引用的对象中所有成员都写完成后,才可以被其他线程引用。
(2)禁止在构造函数中将this复制给外边的对象引用,否则可能导致其他线程看到初始化不完全的实例。
原文地址:https://www.cnblogs.com/shuimuzhushui/p/11336130.html