线程安全但快速访问“最终最终”变量?

时间:2013-08-09 08:18:42

标签: java synchronization java-memory-model

我的服务器有点像这样:

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版本),我必须让工作者不稳定,同步它或其他一些。

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

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

4 个答案:

答案 0 :(得分:3)

关于volatile的注释:标记变量volatile比使用同步更便宜(它不涉及锁定),并且通常便宜,你不会注意到。特别是,在x86架构上,读取volatile变量的成本并不比读取非易失性变量的成本高。但是写入volatile会更加昂贵,而且变量是易失性的,这可能会阻止一些编译器优化。

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

你没有那么多选择。最后,它归结为确保您的工人安全出版。安全的出版习语包括:

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

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

答案 1 :(得分:1)

您应该声明工作人员易失性

由于两个原因

  1. 如果您没有将其声明为易失性而不是由于重新排序和可见性效果 您可以看到对未完成的构造工作对象的非空引用。因此你可以 有不良影响。如果您使用all使您的工作人员不可变,则不会发生这种情况 最终变量。

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

  3. 总结如果工人是不可变的而不是第2点你应该使它变得不稳定。 始终避免不可预测的结果。声明它不稳定会处理这些问题。

答案 2 :(得分:1)

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

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之间存在同步点。

答案 3 :(得分:0)

我考虑使用java.util.concurrent.atomic.AtomicReference 如果它为null,则抛出或等待并再次尝试(如果init足够快)。