【发布时间】:2015-04-26 20:54:20
【问题描述】:
我阅读了this question 关于如何进行双重检查锁定:
// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
FieldType getField() {
FieldType result = field;
if (result == null) { // First check (no locking)
synchronized(this) {
result = field;
if (result == null) // Second check (with locking)
field = result = computeFieldValue();
}
}
return result;
}
我的目标是在没有 volatile 属性的情况下延迟加载字段(不是单例)。初始化后字段对象永远不会改变。
经过一些测试我的最终方法:
private FieldType field;
FieldType getField() {
if (field == null) {
synchronized(this) {
if (field == null)
field = Publisher.publish(computeFieldValue());
}
}
return fieldHolder.field;
}
public class Publisher {
public static <T> T publish(T val){
return new Publish<T>(val).get();
}
private static class Publish<T>{
private final T val;
public Publish(T val) {
this.val = val;
}
public T get(){
return val;
}
}
}
由于不需要 volatile,其好处可能是更快的访问时间,同时仍保持可重用 Publisher 类的简单性。
我使用 jcstress 对此进行了测试。 SafeDCLFinal 按预期工作,而 UnsafeDCLFinal 不一致(如预期)。在这一点上,我 99% 确定它有效,但请证明我错了。使用mvn clean install -pl tests-custom -am 编译并使用java -XX:-UseCompressedOops -jar tests-custom/target/jcstress.jar -t DCLFinal 运行。下面的测试代码(主要是修改过的单例测试类):
/*
* SafeDCLFinal.java:
*/
package org.openjdk.jcstress.tests.singletons;
public class SafeDCLFinal {
@JCStressTest
@JCStressMeta(GradingSafe.class)
public static class Unsafe {
@Actor
public final void actor1(SafeDCLFinalFactory s) {
s.getInstance(SingletonUnsafe::new);
}
@Actor
public final void actor2(SafeDCLFinalFactory s, IntResult1 r) {
r.r1 = Singleton.map(s.getInstance(SingletonUnsafe::new));
}
}
@JCStressTest
@JCStressMeta(GradingSafe.class)
public static class Safe {
@Actor
public final void actor1(SafeDCLFinalFactory s) {
s.getInstance(SingletonSafe::new);
}
@Actor
public final void actor2(SafeDCLFinalFactory s, IntResult1 r) {
r.r1 = Singleton.map(s.getInstance(SingletonSafe::new));
}
}
@State
public static class SafeDCLFinalFactory {
private Singleton instance; // specifically non-volatile
public Singleton getInstance(Supplier<Singleton> s) {
if (instance == null) {
synchronized (this) {
if (instance == null) {
// instance = s.get();
instance = Publisher.publish(s.get(), true);
}
}
}
return instance;
}
}
}
/*
* UnsafeDCLFinal.java:
*/
package org.openjdk.jcstress.tests.singletons;
public class UnsafeDCLFinal {
@JCStressTest
@JCStressMeta(GradingUnsafe.class)
public static class Unsafe {
@Actor
public final void actor1(UnsafeDCLFinalFactory s) {
s.getInstance(SingletonUnsafe::new);
}
@Actor
public final void actor2(UnsafeDCLFinalFactory s, IntResult1 r) {
r.r1 = Singleton.map(s.getInstance(SingletonUnsafe::new));
}
}
@JCStressTest
@JCStressMeta(GradingUnsafe.class)
public static class Safe {
@Actor
public final void actor1(UnsafeDCLFinalFactory s) {
s.getInstance(SingletonSafe::new);
}
@Actor
public final void actor2(UnsafeDCLFinalFactory s, IntResult1 r) {
r.r1 = Singleton.map(s.getInstance(SingletonSafe::new));
}
}
@State
public static class UnsafeDCLFinalFactory {
private Singleton instance; // specifically non-volatile
public Singleton getInstance(Supplier<Singleton> s) {
if (instance == null) {
synchronized (this) {
if (instance == null) {
// instance = s.get();
instance = Publisher.publish(s.get(), false);
}
}
}
return instance;
}
}
}
/*
* Publisher.java:
*/
package org.openjdk.jcstress.tests.singletons;
public class Publisher {
public static <T> T publish(T val, boolean safe){
if(safe){
return new SafePublish<T>(val).get();
}
return new UnsafePublish<T>(val).get();
}
private static class UnsafePublish<T>{
T val;
public UnsafePublish(T val) {
this.val = val;
}
public T get(){
return val;
}
}
private static class SafePublish<T>{
final T val;
public SafePublish(T val) {
this.val = val;
}
public T get(){
return val;
}
}
}
使用 java 8 测试,但至少应该可以使用 java 6+。 See docs
但我想知道这是否可行:
// Double-check idiom for lazy initialization of instance fields without volatile
private FieldHolder fieldHolder = null;
private static class FieldHolder{
public final FieldType field;
FieldHolder(){
field = computeFieldValue();
}
}
FieldType getField() {
if (fieldHolder == null) { // First check (no locking)
synchronized(this) {
if (fieldHolder == null) // Second check (with locking)
fieldHolder = new FieldHolder();
}
}
return fieldHolder.field;
}
甚至可能:
// Double-check idiom for lazy initialization of instance fields without volatile
private FieldType field = null;
private static class FieldHolder{
public final FieldType field;
FieldHolder(){
field = computeFieldValue();
}
}
FieldType getField() {
if (field == null) { // First check (no locking)
synchronized(this) {
if (field == null) // Second check (with locking)
field = new FieldHolder().field;
}
}
return field;
}
或者:
// Double-check idiom for lazy initialization of instance fields without volatile
private FieldType field = null;
FieldType getField() {
if (field == null) { // First check (no locking)
synchronized(this) {
if (field == null) // Second check (with locking)
field = new Object(){
public final FieldType field = computeFieldValue();
}.field;
}
}
return field;
}
我相信这会基于this oracle doc:
final 字段的使用模型很简单:在对象的构造函数中设置对象的 final 字段;并且不要在对象的构造函数完成之前在另一个线程可以看到它的地方写入对正在构造的对象的引用。如果遵循这一点,那么当另一个线程看到该对象时,该线程将始终看到该对象最终字段的正确构造版本。它还将看到至少与最终字段一样最新的最终字段引用的任何对象或数组的版本。
【问题讨论】:
-
就算成功了,又有什么好处呢?
-
@isnot2bad 省略 volatile 可能会更快地访问该字段。
-
如果没有
volatile,它将无法工作。这都是关于可见性,而不是原子性。如果field不是volatile,则一个线程的field的写入操作可能永远不会对另一个线程的读取操作可见。摆弄是没有意义的——线程安全的单例已经解决了。 -
@isnot2bad 我要提到double checked locking is a broken system(渴望初始化> 懒惰),但他没有提到这是一个单身人士
-
@Kicsi 在您的情况下,外部
field不是final。
标签: java multithreading final java-memory-model double-checked-locking