Java并发编程5-Java存储模式(四)

2014-11-23 23:34:46 · 作者: · 浏览: 2
的小技巧被发明出来降低同步的影响,DCL就是其中一种,但却是丑陋的那种,看下面的代码(这段代码来自这里http://www.iteye.com/topic/260515):

public class LazySingleton {
	private int someField;

	private static LazySingleton instance;

	private LazySingleton() {
		this.someField = new Random().nextInt(200) + 1; // (1)
	}

	public static LazySingleton getInstance() {
		if (instance == null) { // (2)
			synchronized (LazySingleton.class) { // (3)
				if (instance == null) { // (4)
					instance = new LazySingleton(); // (5)
				}
			}
		}
		return instance; // (6)
	}

	public int getSomeField() {
		return this.someField; // (7)
	}
}


DCL认为程序最坏的情况就是看到instance的过期值(即为null),这时,DCL将在加锁的情况下再做一次判断,这样能够规避风险,确保获取到instance的最新值。但实际情况却比这更加糟糕,因为线程可以在获取到instance的当前值时,instance内部变量的状态还是过期的,为什么呢?变量someField,它会在构造函数中初始化,当语句2的判断认为instance不为null后返回instance后,因为someField的写入和读取并没有happens-before关系,因此getSomeField并不能保证获取到someField的最新值(根据可见性原则)。
J:很简单,我们为getSomeField方法添加同步就解决这个问题了嘛:
public synchronized int getSomeField() {
return this.someField; // (7)
}
T:但这样任然是不正确的。
J:为什么?
T:你仔细观察的话,语句5和7并没有使用同一个锁,如果要加锁,就需要这样:

	public int getSomeField() {
		synchronized(LazySingleton.class){
			return this.someField; // (7)
		}
	}


但如果这样做的话,就又会存在同步带来的效率问题了,其实在JDK 5之后,将instance声明为volatile就可以了,如下:
private volatile static LazySingleton instance;
J:volatile还能使对象内部的变量也成为可见的?
T:不能。这里之所以正确,是因为在添加了volatile之后,代码就存在以下的happens-before关系了:
1)语句1写入someField值happens-before语句5对instance的写入(程序次序法则);
2)语句5对instance的写入happens-before语句2中获取instance(volatile变量法则);
3)语句2获取instance happens-bufore语句7返回someField(程序次序法则)。
根据传递性法则,语句1写入someField值happens-before语句7返回someField,由此保证了DCL的正确性,也不会导致性能有太多的下降。
J:好复杂。
T:无论怎样,现在DCL已经不再使用了,这里只是将它作为一个学习的例子,如果你需要使用延迟初始化,现在有更好的方法:

public class LazySingleton {
	private int someField;

	private LazySingleton() {
		this.someField = new Random().nextInt(200) + 1; 
	}

	private static class LazySingletonHolder{
		public static LazySingleton instance = new LazySingleton();
	}

	public static LazySingleton getInstance() {
		return LazySingletonHolder.instance;
	}

	public int getSomeField() {
		return this.someField; 
	}
}


JVM将会把LazySingletonHolder的初始化延迟到真正使用它的时刻[JLS 12.4.1]。由于instance在静态初始阶段进行初始化的,所以不需要额外的同步。线程第一次调用getInstance时,引起LazySingletonHolder的加载和初始化,这时,instance将完成初始化。
到这里,DCL的例子也就结束了。希望你对JMM已经有了一个全面的认识,这对于我们后面对并发容器的分析非常重要。非常感谢你一直坚持到现在,下次再见了。
J:好的,下次再见。