通信 :是指线程之间以何种机制来交换信息,通信机制为共享内存 和 消息传递; 同步 :是指程序中用于控制不同线程间操作发生相对顺序的机制;
Java 中实例域,静态域和数组元素都存储在堆内存中,堆内存在线程之间共享,而局部变量,方法参数和异常处理器参数不会在线程之间共享
JMM 定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读写共享变量的副本。
线程A 和线程B 之间通信的步骤 :
线程 A 把本地内存 A 中更新锅的共享变量刷新到主内存中去;线程 B 到主内存中去读取线程 A 之前已更新过的共享变量;在 JMM 中如果一个操作执行的结果需要对另外一个操作可见,那么这两个操作之间必须要存在 happens-before 关系,两个操作即可以在一个线程之内,也可以在不同线程之间
规则如下 :
程序顺序规则 :一个线程中的每个操作,happens-before 于该线程中的任意后续操作;监视器锁规则 :对一个锁的解锁,happens-before 于随后对这个锁的加锁;volatile 变量规则 :对于一个volatile 域的写, happens-before 于任意后续对这个 volatile域的读;传递性 :如果 A happens-before B,而且 B happens-before C ,那么 A happens-before C;假设线程 A 执行 writer() 方法之后,线程 B 执行 reader() 方法:
根据程序次序规则: 1 happens-before 2;3 happens-before 4根据 volatile 规则:2 happens-before 3根据 happens-before 的传递性规则,1 happens-before 4volatile 写的内存语义: 当写一个 volatile 变量时,JMM 会把该线程对相应的本地内存中; volatile 读的内存语义 :当读一个volatile 变量时,JMM会把该线程对应的本地内存设为无效,线程接下来从主存中读取共享变量;
为了实现 volatile 内存语义,JMM 会限制编译器重排序 和 处理器重排序
为了实现 volatile 内存语义,编译器在生成字节码时,会在指令序列插入内存屏障来禁止特定类型的处理器重排序;
JMM内存屏障插入策略 :
在每个 volatile 写操作的前面插入 StoreStore 屏障;在每个 volatile 写操作的后面插入 StoreLoad 屏障;在每个 volatile 读操作的后面插入 LoadLoad 屏障;在每个 volatile 读操作的后面插入 LoadStore 屏障;锁除了让临界区互斥执行,还可以让释放锁的线程向获取同一个锁的线程发消息;
happens-before 关系图 :
锁释放的内存语义 :当线程释放锁时,JMM 会把该线程对应的本地内存中的共享变量刷新到主内存中; 锁获取的内存语义 :当线程获取锁时。JMM 会把该线程线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量;
锁释放与 volatile 写有着相同的内存语义;锁获取与 volatile 读有相同的内存语义;
ReentrantLock 分为公平锁 和 非公平锁;
公平锁 : 加锁方法 lock () 调用轨迹如下:
ReentrantLock : lock();FariSync : lock() ;AbstractQueuedSynchronizer : acquire ( int arg );ReentrantLock : tryAcqurie ( int acquires) ;解锁方法 unlock () 调用轨迹如下:
ReentrantLock : unlock () ;AbstractQueuedSynchronizer : release ( int arg );Sync : tryRerlase ( int releases) ;非公平锁 : 加锁方法**lock ()**调用轨迹如下:
ReentantLock : lock () :NonfairSync : Lock () ;AbstractQueuedSynchronizer : compareAndSetState ( int expect, int update);解锁方法 unlock () 调用轨迹如下:
ReentrantLock : unlock () ;AbstractQueuedSynchronizer : release ( int arg );Sync : tryRerlase ( int releases) ;写 final 域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final 域已经被正确初始化过了,而普通域不具有这个保障;
在一个线程中,初次读对象引用与初次读该对象包含的 final 域,JMM禁止处理器重排序这两个操作,编译器会在读 final 域操作之前插入一个 LoadLoad 屏障;
读 final 域的重排序规则可以确保 :在读一个对象的 final 域之前,一定会先读包含这个 final 域的对象的引用;