【问题标题】:Locking strategy with PropertyChangeListener使用 PropertyChangeListener 锁定策略
【发布时间】:2009-09-24 11:07:40
【问题描述】:

我已经定义了一个具有许多“可观察”属性的类。该类在内部包含一个执行 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() ...
  }
}

我的问题是:我是否应该在持有类锁的同时避免触发属性更改事件? ... 或者也许我应该从单个专用线程(例如“event-multicaster”线程)触发属性更改事件?

当前的设计导致死锁,线程 A 在外部类 Bar 上取出锁,然后尝试调用 Foo 上的方法并取出第二个锁。然而,与此同时,I/O 线程调用setState(State) 获取Foo 上的锁,这会将属性更改事件传播到包含类Bar 并尝试获取此类上的锁......结果陷入僵局。换句话说,属性改变回调的设计意味着我无法有效地控制我的锁被获取的顺序。

我目前的解决方法是设置状态 volatile 并删除 synchronized 关键字,但这似乎是一个杂项;一方面,这意味着无法保证触发属性更改事件的顺序。

【问题讨论】:

    标签: java multithreading concurrency locking propertychanged


    【解决方案1】:

    如果您需要让其他线程从通知循环回调到您的类,那么您需要减少同步范围,使用同步块而不是同步整个消息(这是从您的帖子中复制的,不知道如果它编译):

    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)。


    编辑,概括答案并添加一些哲学。

    首先咆哮:多线程编程很难。任何告诉你不同的人都试图向你推销一些东西。在多线程程序中创建大量相互依赖的连接会导致微妙的错误,如果不是彻底的疯狂的话。

    据我所知,简化多线程编程的最佳方法是编写具有明确定义的起点和终点的独立模块——换句话说,actor model。单个actor是单线程的;您可以轻松地单独理解和测试它。

    纯actor模型非常适合事件通知:actor被调用以响应事件,并执行一些动作。它不关心自开始以来是否触发了另一个事件。有时,这还不够:您需要根据另一个线程管理的状态做出决定。没关系:参与者可以(以同步方式)查看该状态并做出决定。

    要记住的关键点(正如 Tom Hawtin 在他的评论中指出的那样)是您现在读取的状态可能与现在一毫秒不同。您不能永远编写假定您知道对象的确切状态的代码。如果您觉得需要这样做,则需要重新考虑您的设计。

    最后的评论:Doug Lea 比你我聪明。不要试图重新发明 java.util.concurrent 中的类。

    【讨论】:

    • 这绝对是一种改进,但不能保证按正确的顺序触发属性更改事件。
    • 欢迎来到多线程编程。这就是为什么我说更好的方法是使用消息队列作为中介。
    • 谢谢 - 我明白你的意思。我想这与我提到有一个用于触发 PropertyChangeEvents 的专用线程有关。专用线程可以从队列中读取和传播,因此 API 不会改变。更好的是,我的 I/O 线程从来没有在我的班级之外冒险......但没有那么好,因为当收到一个事件时,不能保证变量仍然具有那个值:-(
    • 如果变量的当前状态很重要,那么就需要使用轮询机制。如果更改的事实很重要,那么您可以使用通知机制。
    • 当然,通过轮询值可能会在获取后立即更改。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多