3 回答
TA贡献1775条经验 获得超8个赞
以下是第71项中建议的惯用语:明智地使用 Effective Java:
如果您需要使用延迟初始化来提高实例字段的性能,请使用double-check idiom。这种习惯用法避免了在初始化字段后访问字段时发生锁定的费用(项67)。习惯用语的想法是检查字段的值两次(因此,将其命名为double-check):一次不锁定,然后,如果该字段似乎未初始化,则第二次锁定。仅当第二次检查表明该字段未初始化时,该调用才会初始化该字段。因为如果该字段已经初始化就没有锁定,所以声明该字段至关重要volatile(项目66)。这是成语:
// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
private FieldType getField() {
FieldType result = field;
if (result != null) // First check (no locking)
return result;
synchronized(this) {
if (field == null) // Second check (with locking)
field = computeFieldValue();
return field;
}
}
该代码可能看起来有些混乱。特别是,对局部变量结果的需求可能不清楚。该变量的作用是确保在已初始化字段的常见情况下,该字段仅被读取一次。尽管不是绝对必要的,但是这可以提高性能,并且通过应用于低级并发编程的标准可以更加优雅。在我的机器上,上述方法比不带局部变量的明显方法快25%。
在1.5版之前,由于volatile修饰符的语义不足以支持它,所以双重检查惯用语不能可靠地工作[Pugh01]。版本1.5中引入的内存模型解决了此问题[JLS,17,Goetz06 16]。如今,仔细检查惯用语是延迟初始化实例字段的首选技术。虽然您也可以将双重检查惯用语应用于静态字段,但没有理由这样做:惰性初始化持有人类惯用语是更好的选择。
参考
有效的Java,第二版
项目71:明智地使用惰性初始化
TA贡献1873条经验 获得超9个赞
使用ThreadLocal的DCL作者:Brian Goetz @ JavaWorld
DCL有什么问题?
DCL依赖于资源字段的不同步使用。这似乎无害,但事实并非如此。要了解为什么,请想象线程A在同步块内部,执行语句resource = new Resource();。而线程B刚刚进入getResource()。考虑此初始化对内存的影响。新资源对象的内存将被分配;Resource的构造函数将被调用,初始化新对象的成员字段;并且SomeClass的字段资源将被分配一个对新创建对象的引用。
class SomeClass {
private Resource resource = null;
public Resource getResource() {
if (resource == null) {
synchronized {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
}
但是,由于线程B不在同步块内执行,因此它们看到这些内存操作的顺序可能与一个线程A的执行顺序不同。B可能会按照以下顺序看到这些事件(并且编译器还可以自由地重新排列这样的指令):分配内存,分配对资源的引用,调用构造函数。假设线程B在分配了内存并设置了资源字段之后但在调用构造函数之前出现。它看到资源不为空,跳过同步块,并返回对部分构造的Resource的引用!不用说,结果既不是预期的也不是期望的。
ThreadLocal可以帮助修复DCL吗?
我们可以使用ThreadLocal来实现DCL习惯用法的明确目标-延迟初始化,而无需在公共代码路径上进行同步。考虑以下(线程安全)DCL版本:
清单2.使用ThreadLocal的DCL
class ThreadLocalDCL {
private static ThreadLocal initHolder = new ThreadLocal();
private static Resource resource = null;
public Resource getResource() {
if (initHolder.get() == null) {
synchronized {
if (resource == null)
resource = new Resource();
initHolder.set(Boolean.TRUE);
}
}
return resource;
}
}
我认为; 这里每个线程将一次进入SYNC块以更新threadLocal值;那么它不会。因此,ThreadLocal DCL将确保线程仅在SYNC块内进入一次。
同步到底是什么意思?
Java将每个线程视为在其具有自己的本地内存的处理器上运行,每个线程都与共享的主内存通信并与之同步。即使在单处理器系统上,由于内存高速缓存的影响以及使用处理器寄存器存储变量的影响,该模型还是有意义的。当线程修改其本地内存中的位置时,该修改最终还将显示在主内存中,并且JMM定义了JVM必须何时在本地和主内存之间传输数据的规则。Java架构师意识到,过于严格的内存模型会严重破坏程序性能。他们试图设计一种内存模型,该内存模型将使程序在现代计算机硬件上运行良好,同时仍提供保证,以允许线程以可预测的方式进行交互。
Java可预测地呈现线程之间的交互的主要工具是synced关键字。许多程序员在强制执行互斥信号量(mutex)时严格考虑同步,以防止一次由多个线程执行关键部分。不幸的是,这种直觉不能完全描述同步的含义。
同步的语义确实的确包括基于信号量状态的相互排斥执行,但是它们还包括有关同步线程与主内存交互的规则。特别是,获取或释放锁会触发内存屏障,即线程的本地内存和主内存之间的强制同步。(某些处理器(例如Alpha)具有用于执行内存屏障的显式机器指令。)当线程退出同步块时,它将执行写屏障-在释放之前,必须将在该块中修改的所有变量清除到主内存中锁。同样,进入同步块时,它执行读取屏障操作-好像本地存储器已失效,并且它必须从主存储器中获取将在该块中引用的所有变量。
添加回答
举报