2013-04-26 45 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