使用 PropertyChangeListener 的锁定策略
我定义了一个具有许多“可观察”属性的类。该类内部包含一个执行 I/O 的线程;例如,
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() ...
}
}
我的问题是:我应该避免在持有类锁的同时触发属性更改事件吗? ...或者也许我应该从单个专用线程(例如“事件多播”线程)触发属性更改事件?
当前的设计导致死锁,线程 A 取出外部类 Bar
上的锁,然后尝试调用 Foo
上的方法并取出第二个方法锁。但是,同时 I/O 线程调用 setState(State)
获取 Foo
上的锁,这会将属性更改事件传播到包含类 Bar
并尝试获取此类的锁...导致死锁。换句话说,属性更改回调设计意味着我无法有效控制获取锁的顺序。
我当前的解决方法是使状态易失性
并删除synchronized
关键字,但这看起来像是一个拼凑;一方面,这意味着无法保证属性更改事件的触发顺序。
I've defined a class with a number of "observable" properties. Internally the class contains a single thread that performs 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() ...
}
}
My question is: Should I avoid firing property change events whilst holding onto the class's lock? ... or perhaps I should be firing property change events from a single dedicated thread (e.g. "event-multicaster" thread)?
The current design is resulting in a deadlock whereby thread A takes out a lock on an external class: Bar
, and then tries to call a method on Foo
and take out a second lock. However, at the same time the I/O thread calls setState(State)
obtaining the lock on Foo
, which propagates a property changed event out to the containing class Bar
and attempts to get the lock on this class ... resulting in deadlock. In other words, the property change callback design means I cannot effectively control the order in which my locks are being obtained.
My current workaround is to make state volatile
and remove the synchronized
keywords but this seems like a kludge; for one thing it means the order in which the property change events are fired is not guaranteed.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
如果您需要让其他线程从通知循环回调到您的类中,那么您需要使用同步块来减少同步范围,而不是同步整个消息(这是从您的帖子中复制的,不知道它是否可以编译) ):
保持锁定的时间尽可能短。并考虑用同步消息队列替换这些回调(查看 java.util.concurrent)。
编辑,概括答案并添加一些哲学。
首先咆哮:多线程编程很难。任何告诉你不同的人都是想向你推销东西。在多线程程序中创建大量相互依赖的连接会导致微妙的错误,即使不是彻底的疯狂。
据我所知,简化多线程编程的最佳方法是编写具有明确定义的起点和终点的独立模块——换句话说,演员模型。单个 Actor 是单线程的;您可以轻松地理解并单独测试它。
纯参与者模型非常适合事件通知:参与者被调用以响应事件,并执行某些操作。它不关心自启动以来是否已触发另一个事件。有时,这还不够:您需要根据另一个线程管理的状态做出决定。没关系:参与者可以(以同步方式)查看该状态并做出决定。
要记住的关键一点(正如 Tom Hawtin 在他的评论中指出的那样)是你现在读到的状态可能与现在的一毫秒有所不同。您永远不可能编写假设您知道对象的确切状态的代码。如果您觉得需要这样做,则需要重新考虑您的设计。
最后的评论:Doug Lea 比你我都聪明。不要尝试重新发明 java.util.concurrent。
If you need to have other threads call back into your class from the notification loop, then you need to reduce the scope of synchronization, using a synchronized block rather than synchronizing the entire message (this is copied from your post, no idea if it compiles):
Hold locks for the shortest amount of time possible. And think about replacing these callbacks with a synchronized message queue (look in java.util.concurrent).
Edit, to generalize on the answer and add some philosophy.
Rant first: multi-threaded programming is hard. Anyone who tells you differently is trying to sell you something. Creating a lot of interdependent connections in a multi-threaded program leads to subtle bugs, if not outright insanity.
The best way that I know of to simplify multi-threaded programming is to write independent modules with well-defined start and stop points -- in other words, the actor model. An individual actor is single-threaded; you can easily comprehend and test it in isolation.
The pure actor model fits well with event notifications: the actor is invoked in response to an event, and performs some action. It doesn't care whether another event has fired since it started. Sometimes, that's not enough: you need to make a decision based on state managed by another thread. That's OK: the actor can look at that state (in a synchronized manner) and make decisions.
The key thing to remember (as Tom Hawtin notes in his comment) is that the state you read now may be different a millisecond from now. You can't ever write code that assumes that you know the exact state of an object. If you feel that you need to do this, you need to rethink your design.
And a final comment: Doug Lea is smarter than you or I. Don't try to reinvent the classes in java.util.concurrent.