通常DCL都是这么写的
public class LazySingleton {
private static LazySingleton m_instance = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (m_instance == null) {
synchronized (LazySingleton.class) {
if (m_instance == null) {
m_instance = new LazySingleton(); /*网上的分析DCL失效原因时,认为是这里构造函数执行之前,使实例成为非 null而被其他线程所夺取cpu。并读取到没有执行构造函数实例应用*/ }
}
}
return m_instance;
}
}在java并发编程实践里面对原子性的描述有2层含义:1.一组语句作为单独的不可分割的单元运行2.一个由同一个锁保护的synchronized块一次只能由一个线程执行
似乎DCL失效时,原子性的第一个含义没做到?是我对synchronized块的原子性认识有错么?
public class LazySingleton {
private static LazySingleton m_instance = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (m_instance == null) {
synchronized (LazySingleton.class) {
if (m_instance == null) {
m_instance = new LazySingleton(); /*网上的分析DCL失效原因时,认为是这里构造函数执行之前,使实例成为非 null而被其他线程所夺取cpu。并读取到没有执行构造函数实例应用*/ }
}
}
return m_instance;
}
}在java并发编程实践里面对原子性的描述有2层含义:1.一组语句作为单独的不可分割的单元运行2.一个由同一个锁保护的synchronized块一次只能由一个线程执行
似乎DCL失效时,原子性的第一个含义没做到?是我对synchronized块的原子性认识有错么?
像
synchronized(this)
{
i++;
}
i++本来不是原子操作,但套在synchronized块里时就是原子操作了啊
是正在构造的过程中,比如说:构造这个对象需要1个小时的时间,在开始构造以后,半个小时的时候,另一个线程来访问,此时的(m_instance == null)不成立,然后这个线程就返回了这个对象的引用。对象是在堆上面进行构造的,引用只是一个指向对象的指针而已,当然可以返回正在构造的对象的引用了。
问题就在于m_instance = new LazySingleton();它不是原子的这跟jvm的内存模型有关系,你可以查一下相关的资料
连double d = 1L;这样的赋值语句都不是原子的,何况是对象的构造!
不好意思写错了double d = 1.0;或者long l = 1L;都不是原子的
Object lock=new Object();
synchronized(lock)
{
double d = 1.0;
}
同理
synchronized (LazySingleton.class) {
m_instance = new LazySingleton();
}
也该是原子的吧?
synchronized(lock)
{
double d = 1.0;
}
如果你将这段写在方法里,那没什么意义。
每个方法都有自己独立的运行时栈,所以每个线程都有一个lock,那么每个线程都能成功的获得lock的实例锁。
所以赋值的操作,当然不是原子的。
private Resource resource = null;
public Resource getResource() {
if (resource == null) {
synchronized {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
}
DCL relies on an unsynchronized use of the resource field. That appears to be harmless, but it is not. To see why, imagine that thread A is inside the synchronized block, executing the statement resource = new Resource(); while thread B is just entering getResource(). Consider the effect on memory of this initialization. Memory for the new Resource object will be allocated; the constructor for Resource will be called, initializing the member fields of the new object; and the field resource of SomeClass will be assigned a reference to the newly created object. However, since thread B is not executing inside a synchronized block, it may see these memory operations in a different order than the one thread A executes. It could be the case that B sees these events in the following order (and the compiler is also free to reorder the instructions like this): allocate memory, assign reference to resource, call constructor. Suppose thread B comes along after the memory has been allocated and the resource field is set, but before the constructor is called. It sees that resource is not null, skips the synchronized block, and returns a reference to a partially constructed Resource! Needless to say, the result is neither expected nor desired.
我知道DCL失败的原因,上面那篇文章讲的更细点,我是想讨论下synchronized块的原子性的问题:一组语句作为单独的不可分割的单元运行。要么都做,要么都不做。根据这个原子性,似乎在单核的cpu(当然也没有超线程)上DCL是不可能失效的,因为原子性线程A在构造函数完成前是不能让出cpu的,而在多核的cpu上,线程b可以在另一个内核上执行,这有可能使得DCl失效。这么分析下来,似乎又违反了java跨平台性,晕了
搞定了,DCL失效原因之2
public class LazySingleton {
private static LazySingleton m_instance = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (m_instance == null) {
synchronized (LazySingleton.class) {
if (m_instance == null) {
m_instance = new LazySingleton(); /*执行到这里是可以发生线程调度的,让其他线程执行,获取cpu*/ }
}
}
return m_instance;
}
}