线程安全但可快速访问"eventually final"变量?



我有一个类似这样的服务器:

class Server {
    private WorkingThing worker;
    public void init() {
         runInNewThread({
             // this will take about a minute
             worker = new WorkingThing();
         });
    }
    public Response handleRequest(Request req) {
         if (worker == null) throw new IllegalStateException("Not inited yet");
         return worker.work(req);
    }
}

正如您所看到的,有处理请求的线程和启动服务器的线程。请求可以在初始化完成之前传入,因此需要使用IllegalStateException进行检查。

现在,为了使这个线程安全(这样请求处理程序线程就不会在init之后看到过时的null值的worker版本),我必须使worker变得易失性,对其进行同步,或者诸如此类。

然而,在init完成后,worker将不再更改,因此它实际上是最终的。因此,似乎任何可能发生的锁争用都是浪费。那么,我在这里能做的最有效的事情是什么?

现在我知道这在实际意义上并不重要(阅读网络请求等繁重的工作,一个锁有什么重要?),但出于好奇,我想知道。

关于volatile的一个注意事项:标记一个变量volatile比使用同步(它不涉及锁)更便宜,而且通常足够便宜,你不会注意到。特别是,在x86体系结构上,读取易失性变量的成本并不比读取非易失性可变成本高。然而,写入volatile的代价更高,而且变量是volatile这一事实可能会阻止一些编译器优化。

因此,使用volatile可能是在您的场景中为您提供最佳性能/复杂性比的选项。

你没有那么多选择。最终,它可以归结为确保员工的安全发布。安全出版习惯用法包括:

  • 从静态初始化程序初始化实例
  • 将对实例的引用标记为最终引用
  • 将对实例的引用标记为volatile
  • 同步所有访问

在您的情况下,只有最后两个选项可用,使用volatile更有效。

您应该声明worker是易失性

有两个原因

  1. 如果由于重新排序和可见性的影响而不将其声明为volatile您可以看到对不完整构造的Worker对象的非null引用。这样你就可以具有不良影响。如果您使用最终变量。

  2. 理论上,您的主线程可能在很长一段时间内看不到工作对象的非null引用。所以要避免。

因此,得出结论,如果worker是不可变的,那么对于第2点,您应该使其不可变。始终避免不可预测的结果。宣布其不稳定将解决这些问题。

对初始请求使用简单锁定:

public synchronized void init() {
    if(worker!=null) return;
    runInNewThread({
        synchronized(Server.this){
            worker = new WorkingThing();
            Server.this.notify();
        }
    });
    this.wait();
}
public Response handleRequest(Request req) {
    if (worker == null) synchronized(this) {
        this.wait();
    }
    return worker.work(req);
}

这是因为在访问worker之间存在同步点。

我使用ava.util.courrent.atomic.AtomicReference如果它为null,请抛出或等待并重试(如果init足够快)。

最新更新