2009-09-24 66 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