java - 我必须将我的共享侦听器成员变量声明为volatile吗?

时间:2011-11-22 17:34:31

标签: java volatile

我有一个简单的类,它在自己的线程中进行一些计算,并将结果报告给监听器。

class Calculator extends Thread {
    protected Listener listener;

    public void setListener(Listener l) {
        listener = l;
    }

    public void run() {
        while (running) {
            ... do something ...

            Listener l = listener;

            if (l != null) {
                l.onEvent(...);
            }
        }
    }
}

如果用户在一段时间内不想要任何事件,则用户可以随时调用 setListener(null)。所以,在 run()函数中,我创建了一个监听器的副本,所以我不能遇到 NullPointerException ,如果监听器设置为null,可能会发生这种情况!= null 条件检查成功后。就我而言,我相信这是同步它的正确选择。

我的问题是:我应该将侦听器成员变量声明为volatile吗?我已经阅读了很多关于volatile的内容,但是所有的例子似乎都是针对基本数据类型(boolean,int,...),而不是Objects。因此,我不确定对象是否应该/可以被声明为volatile。我相信我必须声明它是volatile,所以线程总是有成员变量的最新版本,但我不确定。

谢谢!

2 个答案:

答案 0 :(得分:5)

是。为了保证Calculator线程看到一个新值,由另一个线程设置,你必须使变量变为volatile。

但是,volatile是一种相当低级别的机制,很少用于客户端代码。我建议你考虑在这个场景中使用java.util.concurrent.AtomicReference,这可以确保这些事情按预期工作。

答案 1 :(得分:3)

如果您使用此方法,则无法保证在setListener(null)返回后,侦听器不会收到事件通知。执行可以如下进行:

Listener l = listener; // listener != null at this point

// setListener(null) executes here

if (l != null) {
    l.onEvent(...);
}

如果需要保证在取消注册后没有事件发布到侦听器,则需要使用synchronized块。将listener声明为volatile无济于事。代码应该是:

public synchronized void setListener(Listener l) {
    listener = l;
}

public void run() {
    while (running) {
        ... do something ...

        synchronized (this) {
            if (listener != null) {
                listener.onEvent(...);
            }
        }
    }
}

如果您想一直避免花费synchronized,可以这样做:

if (listener != null) {
    synchronized (this) {
        if (listener != null) {
            listener.onEvent(...);
        }
    }
}

这会产生一个轻微的风险,即在设置非空侦听器后您将错过一个事件。将listener声明为volatile可能会解决此问题。