2012-06-25 37 views
5

我對同步實例方法和靜態方法感到困惑。 我想寫一個線程安全類如下:靜態和實例方法上的同步

public class safe { 

    private final static ConcurrentLinkedQueue<Object> objectList= 
     new ConcurrentLinkedQueue<Object>(); 

    /** 
    * retrieves the head of the object and prints it 
    */ 
    public synchronized static void getHeadObject() { 
     System.out.println(objectList.peek().toString()); 

    } 

    /** 
    * creates a new object and stores in the list. 
    */ 
    public synchronized void addObject() { 
     Object obj=new Object(); 
     objectList.add(obj); 

    } 
} 

同步的靜態方法將鎖定safe.class鎖和一個實例方法同步將鎖定在此。而因此不一致的狀態將達到。

如果我想爲下面的代碼片段實現一致的狀態,那麼如何實現?

+1

爲什麼'addObject'一個實例方法?爲什麼不靜態?爲什麼你要同步一個併發對象呢?他們已經是線程安全的。 – Wug

+0

你的意思是不一致的狀態是什麼? –

+0

我假設他的意思是'隊列 objectList =新列表'而不是'ConcurrentLinkedQueue objectList = new ConcurrentLinkedQueue '因此我們得到他所問的實際問題的答案。 –

回答

1

編輯:我假設你的意思是Queue<Object> objectList而不是ConcurrentLinkedQueue<Object> objectListConcurrentLinkedQueue<Object>已經爲您完成了所有的線程安全,這意味着您可以致電objectList.peek(),而無需擔心競賽狀況。如果您正在開發多線程程序,但這對於瞭解線程安全性並不那麼好。

您的方法不需要是​​,假設您一次只有一個線程在對象的一個​​實例上運行,但是如果您需要有多個實例都引用相同的靜態類變量,那麼您需要​​在類變量,像這樣:

public static void getHeadObject() { 
    synchronized(safe.objectList) { 
     System.out.println(objectList.peek().toString()); 
    } 
} 

這將鎖定objectList和不允許它被讀取或者一旦該程序是同步塊內的任何其它線程寫入。對於其他所有方法,執行​​。

注:

但是,因爲你正在做的只有一個簡單的GET操作List.peek(),你真的不需要,因爲在競爭條件在objectList同步,它會得到的任何一個值List或其他。競態條件的問題是當執行多個複雜的讀/寫操作時,它們之間的值發生變化。

舉例來說,如果你有一個類PairIntPairInt.xPairInt.y領域,與約束x = 2y,你想做

System.out.println(myIntPair.x.toString() + ", " + myIntPair.y.toString()); 

,而另一個線程正在更新的xy的價值同時,

myIntPair.y = y + 3; 
myIntPair.x = 2 * y; 

和寫線程修改myIntPair你讀線程之間和myIntPair.y.toString()您可能會得到一個看起來像(10, 8)的輸出,這意味着如果您在假設x == 2 * y可能會導致程序崩潰。

在這種情況下,你的閱讀需要使用​​,但對於像peek()一個簡單object更簡單的事情,被添加或刪除,不是修改而在隊列中,​​可以,在大多數情況下被丟棄。實際上,對於string,int,bool等,應該刪除簡單讀取的​​條件。

但是,對於非明確線程安全的操作,即已由java處理的操作,寫入操作始終應爲​​。而一旦你獲得一個以上的資源,或要求您的資源保持不變,在整個操作過程爲你做邏輯的多行,那麼你必須USE​​

+0

他已經在'getHeadObject'的聲明中使用了synchronized關鍵字,它只是在safe.class上進行同步。 – Wug

+0

我認爲Hans是正確的,因爲在這種情況下我們需要非常小心,如果靜態字段需要獨佔訪問,任何同步實例方法都應該有一個顯式同步塊來訪問靜態字段。 –

+0

這根本沒有任何幫助......兩個安全對象將訪問同一個objectList,如果你正在同時對這兩個對象進行同步,'synchronized'將允許你同時寫入兩個對象,因爲你不是明確地使用(只要vm可以告訴)同一個對象。 –

2

首先的ConcurrentLinkedQueue不需要明確同步。見this answer

其次,你總是可以同步對象,你正在訪問:

public class safe { 

     private final static ConcurrentLinkedQueue<Object> objectList= 
      new ConcurrentLinkedQueue<Object>(); 

     /** 
     * retrieves the head of the object and prints it 
     */ 
    public static void getHeadObject() { 
     synchronized(objectList){ 
      System.out.println(objectList.peek().toString()); 
     } 

    } 

     /** 
     * creates a new object and stores in the list. 
     */ 
    public void addObject() { 
      Object obj=new Object(); 
     synchronized(objectList){ 
      objectList.add(obj); 
     } 

    } 
} 
+0

@assylias:「複製粘貼」,對不起=) –

0

幾點意見:

  • Java約定:
    • 類名應該是首字母大寫(即調用你的類Safe,而不是safe
    • static之前​​的方法聲明
    • staticfinal前場聲明
  • 正如其他人已經說過,ConcurrentLinkedQueue已經是線程安全的,所以沒有必要在你給的例子同步。
  • 混合靜態和非靜態方法你做的方式看起來很奇怪。
  • 假設您的實際用例更加複雜,並且您需要一種方法來運行原子操作,那麼您的代碼不起作用,正如您所指出的那樣,因爲2個同步方法不會在同一臺顯示器上同步:
public static synchronized getHeadObject(){} //monitor = Safe.class 
public static synchronized addObject(){} //monitor = this 

所以回答您的具體問題,你可以使用一個單獨的靜態對象的鎖:

public class Safe { 

    private static final ConcurrentLinkedQueue<Object> objectList = 
      new ConcurrentLinkedQueue<Object>(); 
    // lock must be used to synchronize all the operations on objectList 
    private static final Object lock = new Object(); 

    /** 
    * retrieves the head of the object and prints it 
    */ 
    public static void getHeadObject() { 
     synchronized (lock) { 
      System.out.println(objectList.peek().toString()); 
     } 
    } 

    /** 
    * creates a new object and stores in the list. 
    */ 
    public void addObject() { 
     synchronized (lock) { 
      Object obj = new Object(); 
      objectList.add(obj); 
     } 
    } 
}