6

我有一个有点像这样的服务器:

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-valued 版本worker),我必须使 worker 不稳定,在其上同步,或诸如此类。

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

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

4

4 回答 4

3

关于 volatile 的说明:标记变量 volatile 比使用同步(它不涉及锁)便宜,而且通常便宜到您不会注意到。特别是在 x86 架构上,读取 volatile 变量的成本并不比读取 non-volatile 变量的成本高。但是,写入 volatile 的成本更高,并且变量是 volatile 的事实可能会阻止某些编译器优化。

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

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

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

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

于 2013-08-09T08:30:12.223 回答
1

你应该声明工人是volatile

因为两个原因

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

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

因此得出结论,如果工人是不可变的,那么对于第 2 点,你应该让它变得易变。始终避免不可预知的结果。声明它 volatile 将解决这些问题。

于 2013-08-09T08:30:53.650 回答
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 的访问之间存在同步点。

于 2013-08-09T08:45:39.823 回答
0

如果它为空,我会考虑使用 ava.util.concurrent.atomic.AtomicReference,或者抛出或等待并重试(如果 init 足够快)。

于 2013-08-09T08:34:56.807 回答