我已经定义了一个具有许多“可观察”属性的类。在内部,该类包含一个执行I / O的线程; e.g。
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
并删除synchronized
个关键字,但这似乎是一个问题;一方面,它意味着不能保证触发属性更改事件的顺序。
答案 0 :(得分:3)
如果你需要让其他线程从通知循环回调到你的类中,那么你需要使用synchronized块来减少同步范围,而不是同步整个消息(这是从你的帖子中复制的,不知道如果它编译):
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。单个actor是单线程的;你可以很容易地理解和测试它。
纯粹的actor模型非常适合事件通知:调用actor以响应事件,并执行一些操作。它并不关心自启动以来是否已经解雇了另一个事件。有时,这还不够:您需要根据另一个线程管理的状态做出决策。没关系:演员可以查看该状态(以同步的方式)并做出决定。
要记住的关键事项(正如Tom Hawtin在评论中指出的那样)是你现在阅读的状态可能与现在相差一毫秒。您不能曾编写代码,假定您知道对象的确切状态。如果您认为需要这样做,则需要重新考虑您的设计。
最后评论:Doug Lea比你或我更聪明。不要试图重新发明java.util.concurrent中的课程。