第三章 Java内存模型 总结
1. JMM通过控制主内存与每个线程的本地内存之间的交互,来为Java程序员提供内存可见性保证。当读一个volatile变量
时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
2. JMM属于语言级的内存模型,它确保在不同的编译器和不同的处理器平台之上,通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证。
3. happens-before
4. 锁的释放和获取的内存语义:
线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息。
线程B获取一个锁,实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。
线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。
5.concurrent包的实现
首先,声明共享变量为volatile。
然后,使用CAS的原子条件更新来实现线程之间的同步。
同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。
6.final的内存语义
通过为final域增加写和读重排序规则,可以为Java程序员提供初始化安全保证:只要对象是正确构造的(被构造对象的引用在构造函数中没有“逸出”),那么不需要使用同步(指lock和volatile的使用)就可以保证任意线程都能看到这个final域在构造函数中被初始化之后的值。
7.双重检查锁定与延迟初始化
在Java多线程程序中,有时候需要采用延迟初始化来降低初始化类和创建对象的开销。双重检查锁定是常见的延迟初始化技术,但它是一个错误的用法。
/**
* 双重检查锁定实现延迟初始化
* @author tanlk
* @date 2017年5月5日下午4:57:33
*/
public
class
DoubleCheckedLocking {
// 1
private
static
DoubleCheckedLocking instance;
// 2
public
static
DoubleCheckedLocking getInstance() {
// 3
if
(instance ==
null
) {
// 4:第一次检查
synchronized
(DoubleCheckedLocking.
class
) {
// 5:加锁
if
(instance ==
null
)
// 6:第二次检查
instance =
new
DoubleCheckedLocking();
// 7:问题的根源出在这里
}
// 8
}
// 9
return
instance;
// 10
}
// 11
}
错误原因:
instance=new Singleton(); new对象时,会执行以下伪代码,但是2,3可能会重排序
memory = allocate();
// 1:分配对象的内存空间
ctorInstance(memory);
// 2:初始化对象
instance = memory;
// 3:设置instance指向刚分配的内存地址
如果发生重排序,另一个并发执行的线程B就有可能在第4行判断instance不为null。线程B接下来将访问instance所引用的对象,但此时这个对象可能还没有被A线程初始化!
解决办法:
1)不允许2和3重排序,把instance声明为volatile类型即可
2)允许2和3重排序,但不允许其他线程“看到”这个重排序,静态内部类实现
public
class
InstanceFactory {
private
static
class
InstanceHolder {
public
static
InstanceFactory instance =
new
InstanceFactory();
}
public
static
InstanceFactory getInstance() {
return
InstanceHolder.instance;
}
}
Java语言规范规定,对于每一个类或接口C,都有一个唯一的初始化锁LC与之对应。从C到LC的
映射,由JVM的具体实现去*实现