我的服务器有点像这样:
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
将不会再次更改,因此它实际上是最终的。因此,似乎任何可能发生的锁争用都是浪费。那么,我能在这里做的最有效的事情是什么?
现在我知道它在实际意义上并不重要(阅读网络请求的所有繁重工作等等,单一锁具有什么作用?),但我想知道是出于好奇。
答案 0 :(得分:3)
关于volatile的注释:标记变量volatile比使用同步更便宜(它不涉及锁定),并且通常便宜,你不会注意到。特别是,在x86架构上,读取volatile变量的成本并不比读取非易失性变量的成本高。但是写入volatile会更加昂贵,而且变量是易失性的,这可能会阻止一些编译器优化。
因此,使用volatile可能是在您的方案中为您提供最佳性能/复杂度的选项。
你没有那么多选择。最后,它归结为确保您的工人安全出版。安全的出版习语包括:
在您的情况下,只有最后两个选项可用,并且使用volatile更有效。
答案 1 :(得分:1)
您应该声明工作人员易失性。
由于两个原因
如果您没有将其声明为易失性而不是由于重新排序和可见性效果 您可以看到对未完成的构造工作对象的非空引用。因此你可以 有不良影响。如果您使用all使您的工作人员不可变,则不会发生这种情况 最终变量。
理论上,您的主线程很可能长时间看不到您的工作对象的非空引用。所以要避免它。
总结如果工人是不可变的而不是第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足够快)。