了解监听器通知器上的同步

Understanding synchronized on listeners notifiers

我有单例线程 class,它有时会调用下面的函数并从它的线程 run() 方法通知监听器:

public class Serial implements Runnable
{
    private ArrayList observers = new ArrayList();
    ...

    public void run()
    {
    notifyListeners(new CS());
    }

    public synchronized void notifyListeners(CS value)
    {
        log.debug("notifying listeners with Control ");
        int os = observers.size();
        for (int i = 0; i < observers.size(); i++)
            {
                MListener observer = (MListener) observers.get(i);
                observer.dataReceived(value);
            }
    }
    ...

    public void addListener(MListener lsn)
    {
    observers.add(lsn);
    }

    public void removeListener(MListener lsn)
    {
    observers.remove(lsn);
    }


}

我只是想知道 synchronizednotifyListeners 方法有何影响?原因之一 - 在调用 notifyListeners 时不允许 add/remove 观察者 from/to ArrayList observers。如果我错了,请纠正我。它还能提供什么?

更新

我已经用 addListenerremoveListener 两种方法更新了我的代码。我想这是错误的,因为这两种方法都不是 synchronized 并且可能从另一个线程调用 ?

IMO 通知上的同步没有意义。如果我理解正确的话,notify 方法只会被你的单例线程调用。

但是您的观察者实现内容可能会被不同的线程访问。必须同步对内部状态的访问。

例如如果你想将给定值保存到观察者的成员,稍后由例如使用。主 GUI 线程您必须同步对此成员的访问:

// called by your notify thread
void dataReceived( CS value)
{
    synchronized (this)
    {
        myValue = value;
    }
}

和:

// called by your GUI main thread:
public CS getValue()
{
    synchronized (this)
    {
        // optional check for not null:
        if ( myValue == null) throw new IllegalStateException();
        logger.debug( "returning value: " + myValue);
        return myValue;
    }
}

如果 CS 是 AtomicXY(例如 AtomicInteger)class,则不需要同步。但是如果你想做的不仅仅是 assigning/returning 值(例如一些检查或日志输出)同步是强制性的。

答案主要取决于您的上下文,因为这可能有两个潜在原因:

  1. 我们希望保护您的侦听器列表免受并发访问和修改,因为它是一个 ArrayList,它不是线程安全的(假设可以在您的代码中的其他地方随时修改该列表被修改后,它受到 synchronized 块的保护,其中 this 作为对象的监视器,否则将无法正确完成 and/or 它将毫无用处。
  2. 我们想阻止某些内部逻辑的并发通知。

在您的情况下,因为您只有一个线程调用 notifyListeners #2 不应该是原因,除非代码所有者假设您将来可以有多个线程执行此任务。

只有当其他线程可以同时修改观察者列表时,#1 的原因才有意义,如果不可能,您可以简单地从方法声明中删除关键字 synchronized,因为它没有用,所以它会白白影响表演。

假设 #1 是原因,你应该使用线程安全的 List CopyOnWriteArrayList 而不是 ArrayList 因为它在大多数读取访问场景中非常有效通常是我们主要阅读而很少修改的观察者列表的情况,那么您的代码将是这样的:

public class Serial implements Runnable {
    private final List<MListener> observers = new CopyOnWriteArrayList<>();
    ...
    public void notifyListeners(CS value) {
        log.debug("notifying listeners with Control ");
        for (MListener observer : observers) {
            observer.dataReceived(value);
        }
    }

I suppose it is mistake since both of these methods are not synchronized and might be called from another thread ?

我确认当前代码不正确,因为由于 public 方法 addListenerremoveListener 以及 observers 列表可以由并发线程修改这些方法不会在使用 this 作为对象监视器的 synchronized 块中修改您的列表,因此 当前代码不是线程安全的 因为它不是防止并发访问您的非线程安全列表。