论坛首页 Java企业应用论坛

Java线程安全兼谈DCL

浏览 25043 次
精华帖 (8) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-01-19  
aabcc 写道
要说清楚这个问题,得先说明一下 JMM 啊,内存模型不摆出来,这个问题永远只能是争个你死我活...

就DLC来讲,本例中的instance的值是存在于heap上的,但是有可能thread为了操作的效率会在自己的stack上copy一份instance的副本,所以在多线程操作的时候,同步操作会导致thread将自己stack中的instance反写回heap中,然后其他的读操作,如果有同步操作才能从heap中得到最新的值,如果不同步读操作,那么有可能该thread的stack中的值还是old的
0 请登录后投票
   发表时间:2011-01-19  
neo_q 写道

    [1]如果thread2执行getInstance()在(2)处发现instance为null,对getInstance()的返回结果继续调用getSomeField()将得到什么?会得到0吗?
    因为在调用构造方法时,thread2做了赋值,而且是做了同步操作的,如果调用的线程本身有做同步操作或者是在同一线程那么可以得到确定的值,如果调用的线程是别的线程且未做同步操作那么值是不确定的,因为从头到尾该线程都未取得该对象的锁,因此无法保证它能获得最新的值
    [2]DCL为什么要double check,能不能去掉(4)处的check?若不能,为什么?
    不能去掉,因为在(2)执行的时候,如果已经进行了判断,之后另外一个线程进行了操作有可能使instance不为null


你再想想,回答得都不对。

neo_q 写道

是因为thread2在调用getPos()时,curX有0,1或2三种可能,同样curY也有0,1或2三种可能,所以getPos()可能返回[0,1], [0,2], [0,3], [1,0], [1,1], [1,2], [2,0], [2,1], [2,2]共九种可能

[0,3]应为[0,0]吧

谢谢指出,已更正。
0 请登录后投票
   发表时间:2011-01-19  
sswh 写道
怎样解释《Java并发编程设计原则与模式》中的这几句话呢?

引用
永远只是在更新对象的成员变量时加锁.
永远只是在访问有可能被更新的对象的成员变量时才加锁.
永远不要在调用其他对象的方法时加锁.


永远只是在更新对象的成员变量时加锁?

LZ的结论是:
marlonyao 写道

规则一:必须对变量的所有写和所有读同步,才能读取到该最新的数据。




还要看第二条,其实不矛盾。如果你看第一章的例子,他读取粒子位置时也是加了锁。
public void draw(Graphics g) {
  int lx, ly;
  synchronized(this) { lx = x; ly = y; }
  g.drawRect(lx, ly, 10, 10);
}


另外,第三条是为了防止发生死锁。
0 请登录后投票
   发表时间:2011-01-19  
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修改过的值。
0 请登录后投票
   发表时间:2011-01-19  
ak121077313 写道
还是没有说明谁给instance  赋值了。。。
进锁之前是null 进锁之后又不是null


还是那句话,进锁之前后instance的值没有变化,只是进锁前看到的陈旧值,进锁后看到的是最新值,希望你已经明白了。
0 请登录后投票
   发表时间:2011-01-20  
marlonyao 写道

thread2读取x的值可能为0,1122334400112233外,还可能为别的完全意想不到的值。一种情况假设jvm对long的写入是先写低4位,再写高4位,那么读取到x的值还可能为112233。


这里是不是弄错了,我翻了下《java并发编程实践》。

引用

The Java Memory Model requires fetch and store operations to be atomic, but for nonvolatile long and double variables, the JVM is permitted to treat a 64-bit(位) read or write as two separate 32-bit operations. If the reads and writes occur in different threads, it is therefore possible to read a nonvolatile long and get back the high 32 bits of one value and the low 32 bits of another
0 请登录后投票
   发表时间:2011-01-20  
marlonyao 写道
ak121077313 写道
还是没有说明谁给instance  赋值了。。。
进锁之前是null 进锁之后又不是null


还是那句话,进锁之前后instance的值没有变化,只是进锁前看到的陈旧值,进锁后看到的是最新值,希望你已经明白了。


明白了
0 请登录后投票
   发表时间:2011-01-20  
明天的昨天 写道
marlonyao 写道

thread2读取x的值可能为0,1122334400112233外,还可能为别的完全意想不到的值。一种情况假设jvm对long的写入是先写低4位,再写高4位,那么读取到x的值还可能为112233。


这里是不是弄错了,我翻了下《java并发编程实践》。

引用

The Java Memory Model requires fetch and store operations to be atomic, but for nonvolatile long and double variables, the JVM is permitted to treat a 64-bit(位) read or write as two separate 32-bit operations. If the reads and writes occur in different threads, it is therefore possible to read a nonvolatile long and get back the high 32 bits of one value and the low 32 bits of another


不好意思,我写错了,应该是4字节。
0 请登录后投票
   发表时间:2011-01-20   最后修改:2011-01-20
挣扎,花了一个多小时看两篇文章,还是不明白为什么1.5后DCL是安全的。太笨了~~
marlonyao 写道
  • 如果thread2执行getInstance()在(2)处发现instance为null,对getInstance()的返回结果继续调用getSomeField()将得到什么?会得到0吗?

  • 如果这个时候是null,应该就能保证不是0了。但是如果不是null,是不是还有可能返回的对象是没有构造完的,所以有可能返回0


    特别对前面一篇文章提到的
    marlonyao 写道

    在java 5中多增加了一条happen-before规则:
        * 对volatile字段的写操作happen-before后续的对同一个字段的读操作。
    利用这条规则我们可以将instance声明为volatile,即:
        private volatile static LazySingleton instance; 
    根据这条规则,我们可以得到,线程Ⅰ的语句(5) -> 语线程Ⅱ的句(2),根据单线程规则,线程Ⅰ的语句(1) -> 线程Ⅰ的语句(5)和语线程Ⅱ的句(2) -> 语线程Ⅱ的句(7),再根据传递规则就有线程Ⅰ的语句(1) -> 语线程Ⅱ的句(7),这表示线程Ⅱ能够观察到线程Ⅰ在语句(1)时对someFiled的写入值,程序能够得到正确的行为。

    线程Ⅰ的语句(1) -> 线程Ⅰ的语句(5). 这个是什么原理,不是应该(5)先执行吗?

    marlonyao 写道

       1. public static LazySingleton getInstance() { 
       2.     if (instance == null) {                                         // (4) 
       3.         synchronized(LazySingleton.class) {                         // (5) 
       4.             if (instance == null) {                                 // (6) 
       5.                 LazySingleton localRef = new LazySingleton(); 
       6.                 instance = localRef;                        // (7) 
       7.             } 
       8.         } 
       9.     } 
      10.     return instance;                                                // (8) 
      11. } 

    这个不行的原因是不是因为没法保证其他线程立刻看到这个线程修改的内容? 那如果把instance 和someField 加上volatile呢?
    0 请登录后投票
       发表时间:2011-01-20  
    @yanical java1.5后的dcl也不安全,将someField加上volatile才是安全的。
    0 请登录后投票
    论坛首页 Java企业应用版

    跳转论坛:
    Global site tag (gtag.js) - Google Analytics