3.1
原文:Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的写入何时对另外一个线程可见。P22
Java内存模型JMM定义了写到本地内存中的数据何时刷新到主存中。
原文:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存存储了该线程的共享变量的副本。P22
线程之间的共享变量存储在主存中,共享变量的副本存储在线程的本地内存(私有内存)。
3.1.2
JMM内存模型(重点掌握绘画)
3.1.3(了解)
从Java源代码到最终实际执行的指令序列,会分别经历下面3种重排序。
3.1.4
内存可见性:线程私有内存及时刷新到主存中,保证其它线程能够及时获取变量最新的值。
3.1.5
happens-before: A happens-before B ,表示A操作的结果对B操作可见。可以简单理解为 A在B之前执行(但不一定)
3.2.2
数据依赖:写后读 a=1; b=a; 写后写 a=1;a=2; 读后写a=b;b=1;
两个操作访问同一个变量并且有写操作,那么这两个操作就称之为有数据依赖。
编译器和处理器不会改变有数据依赖关系的两个操作的顺序。
3.2.2
编译器和处理器可能会对操作进行重排序。不管怎么重排序,程序的运行结果不允许被改变。
重排序的目的是在不该程序执行结果的前提下,尽可能提高程序执行效率。
单线程程序为程序员创建了一个幻觉:程序是 “顺序” 执行的。
3.3.2
顺序一致性内存模型:一种理论参考模型。特点(1)线程任务顺序执行(2)每个操作都是原子操作并立即对其它线程可见
3.3.3
临界区内的代码可以重排序。P34
同步代码块中的代码可以重排序,不影响程序的结果。
3.4.3
volatile内存语意:直接操作主存,废弃本地内存
当写一个volatile变量的时候,JMM会把线程对应的本地内存中的共享变量的值刷新到主内存中。
当读一个volatile变量的时候,JMM会把该线程对应的本地内存置为无效。线程接下来从主内存中读取共享变量的值。
3.5.2
锁的内存语意:
当线程释放锁的时候,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。P49
当线程获取锁的时候,JMM会把该线程对应的本地内存置为无效,并重新从主存中获取。
synchronized与volatile一样,会让共享变量的缓存无效。在释放锁的时候,将本地内存中的共享变量刷新到主内存中。在获取锁的时候,所有本地的内存将会失效,必须从主存中再次读取。
A释放锁,A写入主存。B获取锁,B读取主存。相当于A写入了B。
3.6.2
JMM禁止编译器把final域的写重排序到构造函数之外。P56
在构造函数执行完毕的时候, final域的变量一定会被初始化,而普通变量就不一定了。所以,另外线程在获取这个对象的这个final修饰的变量的时候,是肯定能够获取到正确的值,而获取普通的变量可能会出现没有初始化的问题。
编译器会在读final域操作的前面插入一个LoadLoad屏障。P57
保证读取的变量一定是已经正确初始化后的变量。
3.6.5
在引用变量被任意线程可见之前,该引用变量指向的对象的final域已经在构造函数中被正确的初始化过了。P59
在构造函数内部,不能让这个被构造对象的引用被其它线程可见。P59 构造函数{ obj = this ; }//this引用在此“逸出”
在构造函数返回前,被构造对象的引用不能被其它线程可见,因为此时的final域可能还没有被初始化。
3.8.2
双重检验锁与延迟初始化的单例模式的问题根源
public static Singleton getInstance() {
if (instance == null) {
synchorinzed(lock) {
if (instance == null)
instance = new Singleton();//根源在这一句
}
}
return instance;
}
下面重排序过程务必能够手写出来。
instance = new Singleton(); 这一行代码可以拆分成三句。
memory = allocate(); //1.分配对象的内存空间
initInstance(memory); //2.初始化对象
instance = memroy; //3.设置instance引用指向这块内存
上面的代码中,2和3可能会被重排序。
另外一个并发线程B可能拿到的是没有被A线程初始化的对象。
解决办法:(1)禁止指令重排序。所以使用 volatile 关键字修饰instance对象
(2)让这个可能会重排序的代码对其它线程不可见。
使用嵌套类延时加载,如果多个线程同时调用getInstance()方法,那么会执行类初始化。类初始化的时候,会有一把锁,用于同步多个线程对同一个类的初始化。线程A在执行类初始化的时候,可能会出现了重排序,但是这个过程对其它线程是不可见的,所以可以保证正确的初始一个单例对象。