我读过这个问题关于如何进行双重检查的锁定:
// 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;
}我的目标是在没有易失性属性的情况下延迟加载字段(而不是单例)工作。字段对象在初始化后从未更改。
在测试了我的最后一种方法之后:
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;
}
}
}这样做的好处可能是由于不需要易失性而缩短了访问时间,同时仍然保持了可重用Publisher类的简单性。
我用jc应激测试了这个。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+一起工作。见文档
但我不知道这是否可行:
// 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;
}我相信这将在这个甲骨文医生基础上起作用:
最终字段的使用模型是一个简单的模型:在对象的构造函数中设置对象的最终字段;不要在对象的构造函数完成之前,在另一个线程可以看到对象的地方写入对正在构造的对象的引用。如果这样做,那么当对象被另一个线程看到时,该线程将始终看到该对象的最终字段的正确构造版本。它还将看到由这些最终字段引用的任何对象或数组的版本,这些字段至少与最后字段一样是最新的。
发布于 2015-05-05 09:09:38
首先要做的是:你想做的事充其量是危险的。当人们试图在期末考试中作弊时,我有点紧张。Java语言为您提供了volatile作为处理线程间一致性的工具.使用它。
无论如何,相关的方法在“Java中的安全发布和初始化”中描述为:
public class FinalWrapperFactory {
private FinalWrapper wrapper;
public Singleton get() {
FinalWrapper w = wrapper;
if (w == null) { // check 1
synchronized(this) {
w = wrapper;
if (w == null) { // check2
w = new FinalWrapper(new Singleton());
wrapper = w;
}
}
}
return w.instance;
}
private static class FinalWrapper {
public final Singleton instance;
public FinalWrapper(Singleton instance) {
this.instance = instance;
}
}
}这是外行的条件,是这样的。当我们将synchronized视为null时,wrapper会产生适当的同步--换句话说,如果我们完全放弃第一个检查并将synchronized扩展到整个方法主体,那么代码显然是正确的。final in FinalWrapper保证当我们看到了非空的wrapper,它是完全构造的,所有的Singleton字段都是可见的--这是从wrapper的读取中恢复过来的。
注意,它在字段中传递的是FinalWrapper,而不是值本身。如果instance是在没有FinalWrapper的情况下发布的,那么所有的赌注都会被取消(从外行人的角度来说,这是不成熟的)。这就是为什么您的Publisher.publish不能正常工作的原因:只通过最后一个字段将值放回、重新读取并不安全地发布它都是不安全的--这非常类似于只将裸露的instance写入。
此外,当您发现空wrapper并使用它的值时,您必须小心地在锁下读取“回退”。第二次(第三次)阅读“回报”中的wrapper也会破坏正确性,使您进入一个合法的种族。
编辑:顺便说一句,如果您发布的对象内部覆盖了final-s,那么您可以减少FinalWrapper的中间人,并发布instance本身。
编辑2:请参见,LCK10-J.使用正确的复核锁定成语形式。,以及注释中的一些讨论。
发布于 2015-04-30 15:25:53
引用@Kicsi提到的“双重检查锁坏了”声明,最后一节是:
双重检查锁定不可变对象 如果Helper是一个不可变的对象,因此Helper的所有字段都是最终的,那么双重检查的锁定就可以工作,而不必使用易失性字段。其思想是,对不可变对象(例如字符串或Integer)的引用应该以与int或float相似的方式运行;读取和写入对不可变对象的引用是原子的。
(重点是我的)
由于FieldHolder是不可变的,所以您实际上不需要volatile关键字:其他线程总是会看到一个正确初始化的FieldHolder。据我所知,在通过FieldType从其他线程访问它之前,总是会对它进行初始化。
但是,如果FieldType不是不可变的,则仍然需要适当的同步。因此,我不确定避免使用volatile关键字会有多大好处。
但是,如果它是不可变的,那么您就根本不需要遵循上述引号的FieldHolder。
发布于 2019-03-28 03:50:29
使用Enum或嵌套的静态类助手来进行延迟初始化,否则,如果初始化不会花费太多(空间或时间),只需使用静态初始化即可。
public enum EnumSingleton {
/**
* using enum indeed avoid reflection intruding but also limit the ability of the instance;
*/
INSTANCE;
SingletonTypeEnum getType() {
return SingletonTypeEnum.ENUM;
}
}
/**
* Singleton:
* The JLS guarantees that a class is only loaded when it's used for the first time
* (making the singleton initialization lazy)
*
* Thread-safe:
* class loading is thread-safe (making the getInstance() method thread-safe as well)
*
*/
private static class SingletonHelper {
private static final LazyInitializedSingleton INSTANCE = new LazyInitializedSingleton();
}“双重检查锁坏了”声明
通过此更改,可以通过声明助手字段为易失性来使双重检查锁定成语工作。这在JDK4和更早的时候是行不通的。
class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null)
helper = new Helper();
}
}
return helper;
}
}https://stackoverflow.com/questions/29883403
复制相似问题