Java内存模型happens-before法则+单例的延迟加载
2013-02-05 09:44
585 查看
什么是happens-before?
happens-before就是“什么什么一定在什么什么之前运行”,也就是保证顺序性。
因为CPU是可以不按我们写代码的顺序执行内存的存取过程的,也就是指令会乱序或并行运行,
只有上面的happens-before所规定的情况下,才保证顺序性。
如:
Java代码
public class Test {
private int a = 0;
private long b = 0;
public void set() {
a = 1;
b = -1;
}
public void check() {
if (! ((b == 0) || (b == -1 && a == 1))
throw new Exception("check Error!");
}
}
对于set()方法的执行:
1. 编译器可以重新安排语句的执行顺序,这样b就可以在a之前赋值。如果方法是内嵌的(inline),编译器还可以把其它语句重新排序。
2. 处理器可以改变这些语句的机器指令的执行顺序,甚到同时执行这些语句。
3. 存储系统(由于被缓存控制单元控制)也可以重新安排对应存储单元的写操作顺序,这些写操作可能与其他计算和存储操作同时发生。
4. 编译器,处理器和存储系统都可以把这两条语句的机器指令交叉执行。
例如:在一台32位的机器上,可以先写b的高位,然后写a,最后写b的低位,(注:b为long类型,在32位的机器上分高低位存储)
5. 编译器,处理器和存储系统都可以使对应于变量的存储单元一直保留着原来的值,
以某种方式维护相应的值(例如,在CPU的寄存器中)以保证代码正常运行,直到下一个check调用才更新。
...
在单线程(或同步)的情况下,上面的check()永远不会报错,
但非同步多线程运行时却很有可能。
并且,多个CPU之间的缓存也不保证实时同步,
也就是说你刚给一个变量赋值,另一个线程立即获取它的值,可能拿到的却是旧值(或null),
因为两个线程在不同的CPU执行,它们看到的缓存值不一样,
只有在synchronized或volatile或final的性况下才能保证正确性,
很多人用synchronized时只记得有lock的功能,而忘记了线程间的可见性问题。
如:
Java代码
public class Test {
private int n;
public void set(int n) {
this.n = n;
}
public void check() {
if (n != n)
throw new Exception("check Error!");
}
}
check()中的 n != n 好像永远不会成立,因为他们指向同一个值,但非同步时却很有可能发生。
另外,JMM不保证创建过程的原子性,读写并发时,可能看到不完整的对象,
这也是为什么单例模式中著名的"双重检查成例"方法,在Java中行不通。(但.Net的内存模型保证这一点)
当然,在Java中单例的延迟加载可以用另一种方案实现(方案四):
方案一:非延迟加载单例类
Java代码
public class Singleton {
private Singleton(){}
private static final Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
方案二:简单的同步延迟加载
Java代码
public class Singleton {
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
}
方案三:双重检查成例延迟加载
目的是避开过多的同步,
但在Java中行不通,因为同步块外面的if (instance == null)可能看到已存在,但不完整的实例。
JDK5.0以后版本若instance为volatile则可行
Java代码
public class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
方案四:类加载器延迟加载
Java代码
public class Singleton {
private static class Holder {
static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return Holder.instance;
}
}
happens-before就是“什么什么一定在什么什么之前运行”,也就是保证顺序性。
因为CPU是可以不按我们写代码的顺序执行内存的存取过程的,也就是指令会乱序或并行运行,
只有上面的happens-before所规定的情况下,才保证顺序性。
如:
Java代码
public class Test {
private int a = 0;
private long b = 0;
public void set() {
a = 1;
b = -1;
}
public void check() {
if (! ((b == 0) || (b == -1 && a == 1))
throw new Exception("check Error!");
}
}
对于set()方法的执行:
1. 编译器可以重新安排语句的执行顺序,这样b就可以在a之前赋值。如果方法是内嵌的(inline),编译器还可以把其它语句重新排序。
2. 处理器可以改变这些语句的机器指令的执行顺序,甚到同时执行这些语句。
3. 存储系统(由于被缓存控制单元控制)也可以重新安排对应存储单元的写操作顺序,这些写操作可能与其他计算和存储操作同时发生。
4. 编译器,处理器和存储系统都可以把这两条语句的机器指令交叉执行。
例如:在一台32位的机器上,可以先写b的高位,然后写a,最后写b的低位,(注:b为long类型,在32位的机器上分高低位存储)
5. 编译器,处理器和存储系统都可以使对应于变量的存储单元一直保留着原来的值,
以某种方式维护相应的值(例如,在CPU的寄存器中)以保证代码正常运行,直到下一个check调用才更新。
...
在单线程(或同步)的情况下,上面的check()永远不会报错,
但非同步多线程运行时却很有可能。
并且,多个CPU之间的缓存也不保证实时同步,
也就是说你刚给一个变量赋值,另一个线程立即获取它的值,可能拿到的却是旧值(或null),
因为两个线程在不同的CPU执行,它们看到的缓存值不一样,
只有在synchronized或volatile或final的性况下才能保证正确性,
很多人用synchronized时只记得有lock的功能,而忘记了线程间的可见性问题。
如:
Java代码
public class Test {
private int n;
public void set(int n) {
this.n = n;
}
public void check() {
if (n != n)
throw new Exception("check Error!");
}
}
check()中的 n != n 好像永远不会成立,因为他们指向同一个值,但非同步时却很有可能发生。
另外,JMM不保证创建过程的原子性,读写并发时,可能看到不完整的对象,
这也是为什么单例模式中著名的"双重检查成例"方法,在Java中行不通。(但.Net的内存模型保证这一点)
当然,在Java中单例的延迟加载可以用另一种方案实现(方案四):
方案一:非延迟加载单例类
Java代码
public class Singleton {
private Singleton(){}
private static final Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
方案二:简单的同步延迟加载
Java代码
public class Singleton {
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
}
方案三:双重检查成例延迟加载
目的是避开过多的同步,
但在Java中行不通,因为同步块外面的if (instance == null)可能看到已存在,但不完整的实例。
JDK5.0以后版本若instance为volatile则可行
Java代码
public class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
方案四:类加载器延迟加载
Java代码
public class Singleton {
private static class Holder {
static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return Holder.instance;
}
}
相关文章推荐
- 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则
- 学习笔记二:Java内存模型以及happens-before规则
- java内存模型(JMM)之happens-before
- 【死磕Java并发】--Java内存模型之happens-before
- Java并发11:Java内存模型、指令重排、内存屏障、happens-before原则
- 【死磕Java并发】-----Java内存模型之happens-before
- Java内存模型之happens-before
- java.util.concurrent包介绍(3)——Happens-before法则
- 学习笔记之Java线程安全杂谈(中)——Java内存模型、happens-before原则和DCL问题
- 死磕Java并发:Java内存模型之happens-before
- 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则
- 【死磕Java并发】-----Java内存模型之happens-before
- Java内存模型之happens-before
- 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则
- Java多线程 -- 深入理解JMM(Java内存模型) -- happens-before俗解
- 程序员学习笔记:指令重排序及Happens-before法则
- Java Happens-before法则
- 指令重排序及Happens-before法则
- 《java并发编程实战》读书笔记13--Java内存模型,重排序,Happens-Before
- Java内存模型之happens-before规则