锁定老帖子 主题:Java线程安全兼谈DCL
精华帖 (8) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2011-01-20
marlonyao 写道 @yanical java1.5后的dcl也不安全,将someField加上volatile才是安全的。
原来一直说有可能thread1创建了对象但是初始化还没完成,thread2就去访问。请问1.5怎么避免这种情况。 |
|
返回顶楼 | |
发表时间:2011-01-21
最后修改:2011-01-21
marlonyao 写道 neo_q 写道 [1]如果thread2执行getInstance()在(2)处发现instance为null,对getInstance()的返回结果继续调用getSomeField()将得到什么?会得到0吗? 因为在调用构造方法时,thread2做了赋值,而且是做了同步操作的,如果调用的线程本身有做同步操作或者是在同一线程那么可以得到确定的值,如果调用的线程是别的线程且未做同步操作那么值是不确定的,因为从头到尾该线程都未取得该对象的锁,因此无法保证它能获得最新的值 [2]DCL为什么要double check,能不能去掉(4)处的check?若不能,为什么? 不能去掉,因为在(2)执行的时候,如果已经进行了判断,之后另外一个线程进行了操作有可能使instance不为null 你再想想,回答得都不对。 也许是我表达不准,也许真的是我搞错了,我再解释一下 (1)对于多线程程序来讲,是否能得到最新值,应该以happen-before规则来判断线程能否得到最新值,如果要保证这种最新值,就必须做同步操作(使得线程的内存和主存同步以此获得最新值),而之所以DLC会有问题,是因为对于某一线程,如果在第一次判断(即操作(2))为false的情况下,那么该线程就不会有任何作同步操作的机会,此外getSomeField本身也非同步方法,因此可能导致getSomeField得到的值不确定 (2)(4)处的check应该有两层含义,第一:它位于同步锁内,所以他保证此时instance是内存中最新值,也能保证instance的field是最新的;第二:如果有别的线程已经初始化了instance,那么他需要判断是否为null,以此决定自己是否需要再new一次。 以上是我的理解,有误之处请指正(最好详细点:)),谢谢。 |
|
返回顶楼 | |
发表时间:2011-01-21
@neo_q 对第一个问题,可能你理解错我的意思了,我的问题假设thread第一次检查时发现instance为null。这时它在有锁情况下进行第二次检测,这是它保证能读到someField的最新值,不会读到0。
对第二个问题,你解释是对的,如果没有(4)处的check,有可能创建多个实例,就完全违背了Singleton模式的原则。 |
|
返回顶楼 | |
发表时间:2011-01-22
添加了大量示意图,数据陈旧性部分作了很大修改。
|
|
返回顶楼 | |
发表时间:2011-01-24
marlonyao 写道 添加了大量示意图,数据陈旧性部分作了很大修改。 marlonyao,是程序员“耐心、有条理”的良好示范。:) |
|
返回顶楼 | |
发表时间:2011-01-24
最后修改:2011-01-24
marlonyao 写道 @neo_q 对第一个问题,可能你理解错我的意思了,我的问题假设thread第一次检查时发现instance为null。这时它在有锁情况下进行第二次检测,这是它保证能读到someField的最新值,不会读到0。
我的理解是对多个线程同时并发的状况下,两个线程之间的状态共享,我现在理解你的意思了 我只所以对DCL这么纠结是曾经被某位牛人在这个问题上给鄙视了,所以我也花了点时间来了解这个问题 |
|
返回顶楼 | |
发表时间:2011-04-21
neo_q 写道 aabcc 写道 要说清楚这个问题,得先说明一下 JMM 啊,内存模型不摆出来,这个问题永远只能是争个你死我活...
就DLC来讲,本例中的instance的值是存在于heap上的,但是有可能thread为了操作的效率会在自己的stack上copy一份instance的副本,所以在多线程操作的时候,同步操作会导致thread将自己stack中的instance反写回heap中,然后其他的读操作,如果有同步操作才能从heap中得到最新的值,如果不同步读操作,那么有可能该thread的stack中的值还是old的 marlonyao 写道 ak121077313 写道 有一点不明白
public static LazySingleton getInstance() { if (instance == null) { // (2) synchronized(LazySingleton.class) { // (3) if (instance == null) { // (4) instance = new LazySingleton(); // (5) } } } return instance; // (6) } thread2如果通过2判断 instance == null 进入4 lz说会读到最新的值 我不明白在没有赋值的情况下为什么instance的值会变动?????? 谁在这个过程中赋值了?难道是锁? instance在thread2的运行过程中一直都没变,只是在没加锁访问时它有可能读不到最新值。在第(4)处加锁访问才能保证读到最新的值。 我一直避免谈得太底层,但多一种理解也是好处的。除了共享内存(shared memory)之外,每个线程都可能有自己缓存,读取线程会先从缓存中读取。对于thread2来说,在(2)处它读到缓存值的null,虽然此时主内存里已经被thread2修改过了,但thread2并不能观察到这点,但到(4)处,由于加了锁,它强制缓存失效,必须从共享内存读取最新值,即thread1修改过的值。 综合上面的引用,我个人的理解是这样子的 public class LazySingleton { private int someField; private static LazySingleton instance; private LazySingleton() { this.someField = 201; // (1) } public static LazySingleton getInstance() { if (instance == null) { // (2) synchronized(LazySingleton.class) { // (3) if (instance == null) { // (4) instance = new LazySingleton(); // (5) } instance.getSomeField(); // thread2这里的值应该是正确的,因为在同步块内,读到的是heap里的someField } instance.getSomeField(); // 这里读到的有可能是thread2自己stack里的someField,此时在解除锁的时候只把instance引用从heap更新到stack中,并没有把somefield更新到stack中,造成了有可能读取到的是0 } return instance; // (6) } public int getSomeField() { return this.someField; // (7) } } |
|
返回顶楼 | |
发表时间:2011-04-21
aabcc 写道 要说清楚这个问题,得先说明一下 JMM 啊,内存模型不摆出来,这个问题永远只能是争个你死我活...
+1. BRS很帅啊。呵呵 |
|
返回顶楼 | |
发表时间:2011-08-05
最后修改:2011-08-05
“必须对变量的所有写和所有读同步,才能读取到该最新的数据。 ”这句话看起很奇怪的,主要是“同步”这个词哈。读取到最新的数据只需要保证内存可见性就可以了。不一定需要用锁。我觉得lz在讲的时候把内存可见性和锁混一起了。volatile就能够保证内存可见性,而锁,是需要synchronize去保证。有锁的情况下,内存可见性一定能够保证,所以对lz的dcl例子中的错误貌似很容易解释。
|
|
返回顶楼 | |
发表时间:2011-08-05
“在java5中,final变量一旦在构造函数中设置完成(前提是在构造函数中没有泄露this引用),其它线程必定会看到在构造函数中设置的值。”看来主贴引用贴的这段话,,羞愧,,,居然现在才知道。
|
|
返回顶楼 | |