2009-09-24 105 views
3

我用一些「可觀察」屬性定義了一個類。該類內部包含一個執行I/O的單個線程;例如使用PropertyChangeListener鎖定策略

public class Foo { 
    private final PropertyChangeSupport support; 
    private State state; 

    public Foo() { this.support = new PropertyChangeSupport(this); } 

    public synchronized State getState() { return state; } 

    public synchronized void setState(State state) { 
    if (this.state != state) { 
     State oldState = this.state; 
     this.state = state; 

     // Fire property change *whilst still holding the lock*. 
     support.firePropertyChange("state", oldState, state); 
    } 
    } 

    public synchronized void start() { 
    // Start I/O Thread, which will call setState(State) in some circumstances. 
    new Thread(new Runnable() ... 
    } 
} 

我的問題是:我應該避免在保持課堂鎖定的情況下解僱屬性更改事件嗎? ... 也許我應該從單個專用線程(例如「event-multicaster」線程)觸發屬性更改事件?

當前的設計會導致死鎖,即線程A取出外部類的鎖:Bar,然後嘗試調用Foo上的方法並取出第二個鎖。但是,I/O線程同時調用setState(State)獲取Foo上的鎖定,該鎖定將屬性更改事件傳播到包含類Bar,並嘗試獲取該類上的鎖定......導致死鎖。換句話說,屬性更改回調設計意味着我無法有效控制獲取鎖的順序。

我目前的解決方法是使狀態volatile和刪除​​關鍵字,但這似乎是一個kludge;一方面它意味着財產變更事件被解僱的順序不能得到保證。

回答

3

如果您需要讓其他線程從通知循環回調到您的類中,那麼您需要使用同步塊來減少同步範圍,而不是同步整個郵件(這是從您的帖子中複製的,不是想法如果它編譯):

public void setState(State state) { 
    State oldState = null; 
    synchronized (this) { 
     if (this.state != state) { 
     oldState = this.state; 
     this.state = state; 
     } 
    } 

    if (oldState != null) 
     support.firePropertyChange("state", oldState, state); 
    } 

保持鎖儘可能短的時間。並考慮用同步消息隊列替換這些回調(查看java.util.concurrent)。


編輯,以概括答案並添加一些哲學。

Rant優先:多線程編程很難。任何告訴你不同的人都試圖向你推銷一些東西。在多線程程序中創建大量相互依賴的連接會導致微妙的錯誤,即使不是徹底的瘋狂。

我所知道的簡化多線程編程的最佳方式是編寫具有明確定義的啓動和停止點的獨立模塊 - 換言之,即actor model。一個單獨的演員是單線程的;您可以輕鬆理解並單獨進行測試。

純粹的演員模型符合事件通知:演員被調用以響應事件,並執行一些操作。它並不關心自從它開始以來是否有其他事件發起。有時候,這還不夠:您需要根據另一個線程管理的狀態做出決定。沒關係:演員可以以同步的方式看待該狀態並做出決定。

要記住的關鍵(正如Tom Hawtin在他的評論中指出的那樣)是現在讀的狀態可能與現在的毫秒不同。你不能有史以來編寫代碼,假設你知道一個對象的確切狀態。如果你覺得你需要這樣做,你需要重新考慮你的設計。

最後的評論:Doug Lea比你或者我聰明。不要試圖重塑在java.util.concurrent中的課程。

+0

這絕對是一種改進,但不能保證以正確的順序觸發屬性更改事件。 – Adamski 2009-09-24 11:30:20

+1

歡迎來到多線程編程。這就是爲什麼我說更好的方法是使用消息隊列作爲中介。 – kdgregory 2009-09-24 11:39:02

+0

謝謝 - 我明白了你的觀點。我想這與我提到有一個專用的線程來觸發PropertyChangeEvents。專用線程可以從隊列中讀取並傳播,因此API不會更改。這也會更好,因爲我的I/O線程從來沒有冒險過我的班級......但沒有那麼好,當收到一個事件時,不能保證變量仍然具有該值:-( – Adamski 2009-09-24 12:07:33