2013-04-26 67 views
7
private volatile Object obj = new MyObject(); 
void foo() 
{ 
    synchronized(obj) 
    { 
    obj.doWork(); 
    } 
} 
void bar() 
{ 
    synchronized(obj) 
    { 
    obj.doWork(); 
    obj = new MyObject(); // <<<< notice this line (call it line-x) 
    } 
} 

假设在某个时间点,线程t_bar正在执行bar(),另一个t_foo正在执行foo,而且t_bar刚刚收购obj,所以t_foo是,实际上,等候。同步对非最终对象

执行bar中的同步块后,foo将开始执行其同步块,对吧?它会看到什么值obj?旧的?或者在bar中设置新的?

(我会希望新值看出,这是编码这种方式整点,但我想知道这是否是一个“安全”的赌注)

+3

@javapirate:我发现它非常粗鲁,你编辑我的帖子只是因为你喜欢你自己的K&R格式风格?我很抱歉,但我必须重新格式化它。 – 2013-04-26 16:39:31

+0

对不起。我想要摆脱很多空的空间。前进! – 2013-04-26 16:42:52

+0

可能有助于理解的一个区别是* objects *或不是final或not-final,引用它们的变量是。锁在对象上,而不在变量上。 – 2013-04-26 17:45:07

回答

0

这是不安全的,破碎。更改锁定的对象不起作用。

当一个线程试图进入一个同步块时,它首先必须计算parens中的表达式,以便找出它需要的锁。如果在此之后锁更改,则线程无法知道它最终获取旧锁并进入同步块。此时,它会看到该对象并对其进行评估,获取新的引用,并使用旧的(现在不相关的)锁调用该方法,并且不保留新锁,即使其他某个线程可能持有该新锁也可以可能会同时在同一个对象上执行该方法。

1

它的行为通常就好像对象引用没有在内部改变一样。原因是测试锁定对象只能进行一次。因此,即使对象内部发生变化,线程仍会继续等待,行为将会重复,就像对象相同[不变]一样。

我试了另一件事。我在创建新对象之后放置了一个sleep语句,然后启动下一个线程,并且如预期的那样,两个线程同时开始工作。 请参阅下面的代码。

public class ChangeLockObjectState { 

    private volatile Object obj = new Object(); 

    void foo() { 
     synchronized (obj) { 
      try { 
       System.out.println("inside foo"); 
       Thread.sleep(10000); 
      } catch (InterruptedException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
     } 
    } 

    void bar() { 
     synchronized (obj) { 
      try { 
       System.out.println("inside bar"); 
       Thread.sleep(5000); 
      } catch (InterruptedException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 

      obj = new Object(); // <<<< notice this line (call it line-x) 

      System.out.println("going out of bar"); 

      try { 

       Thread.sleep(5000); 
      } catch (InterruptedException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 

      System.out.println("wait over"); 

     } 
    } 

    /** 
    * @param args 
    * @throws InterruptedException 
    */ 
    public static void main(String[] args) throws InterruptedException { 
     final ChangeLockObjectState test = new ChangeLockObjectState(); 

     new Thread(new Runnable() { 

      @Override 
      public void run() { 
       test.bar(); 

      } 
     }).start(); 

     Thread.sleep(6000); 

     new Thread(new Runnable() { 

      @Override 
      public void run() { 
       test.foo(); 

      } 
     }).start(); 

    } 

} 
-2

将读取新值obj

从标准的部分上Happens before

的挥发性场(§8.3.1.4)之前发生的那场以后的每一次读取写入。

从共享变量的定义:

所有实例字段,静态字段,和数组元素存储在堆存储器。在本章中,我们使用术语变量来指代字段和数组元素。 线程之间永远不会共享局部变量(第14.4节),形式方法参数(第8.4.1节)和异常处理程序参数(第14.20节),并且不受内存模型的影响。

obj同步块的内部的读取是从表达式obj的初始评估分开,以确定哪些对象的内置的监视器来锁定。 obj的重新分配将在第一次阅读之前发生,但不是第二次。由于objvolatile字段,因此第二次读取必须看到更新后的值obj

+0

它如何链接到这个问题? – Lokesh 2013-04-26 16:46:26

+0

糟糕,你说得对。回答了错误的问题:-( – 2013-04-26 17:05:10

+0

更改为回答正确的问题 – 2013-04-26 17:17:50

0

显示新值。即使没有制作objvolatile,它也能正常工作。这是因为同步仍旧保留在旧对象上,并在等待线程(t_foo)进入内部时提供新值的可见性。下面是测试:

public class Main3 { 
    private MyObject obj = new MyObject(1); 

    void foo() 
    { 
     synchronized(obj) 
     { 
      System.out.println(obj.number); 
      obj.doWork(); 
     } 
    } 

    void bar() 
    { 
     synchronized(obj) 
     { 
      System.out.println(obj.number); 

      obj.doWork(); 

      //force the foo thread to wait at the synchronization point 
      for(int i = 0; i < 1000000000l; i++); 

      obj = new MyObject(2); // <<<< notice this line (call it line-x) 
     } 
    } 

    public static void main(String[] args) throws InterruptedException { 
     final Main3 m3 = new Main3(); 

     Thread t1 = new Thread(new Runnable() { 
      @Override 
      public void run() { 
       m3.bar(); 
      } 
     }); 

     Thread t2 = new Thread(new Runnable() { 
      @Override 
      public void run() { 
       m3.foo(); 
      } 
     }); 

     t1.start(); 
     t2.start(); 
    } 
} 

class MyObject { 
    int number; 

    public MyObject(int number) { 
     this.number = number; 
    } 

    public void doWork() { 
    } 
} 
2

在您所描述的确切情况,是的,obj Foo的synchronized块里面读会看到由以前的酒吧的synchronized块设置新值。

有趣的是,它并不总是发生在这种确切的情况。该程序不是线程安全的,例如,如果在退出bar()之后立即调用另一个线程bar(),而foo线程锁定旧对象。条线程锁定在新对象上,所以两个线程并发执行,都在同一个新对象上执行obj.doWork()

我们或许可以部分地通过

// suppose this line happens-before foo()/bar() calls 
MyObject obj = new MyObject(); 

void foo() 
    while(true) 
     MyObject tmp1 = obj; 
     synchronized(tmp1) 
      MyObject tmp2 = obj; 
      if(tmp2==tmp1) 
       tmp2.doWork(); 
       return; 
      // else retry 

这至少保证修复它的obj.doWork()在同一OBJ当前没有调用,因为obj.doWork()只能发生在一个同步块锁定完全相同的obj

+0

'foo'如何锁定'old'对象?'bar'退出后,'obj'在任何地方都会有新的值,是吗? – 2013-04-26 17:28:35

+0

@ One No,foo仍然会锁定旧对象。请参阅http://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.19,或查看答案对我的问题2 edits ago(我在哪里回答*那个问题 – 2013-04-26 17:30:18

+0

好吧,它保存着旧对象的锁,据我了解,但它看起来像任何引用*内*同步块将指新value ob' obj' ...? – 2013-04-26 17:34:15