这个实现是否适用于Java中无锁线程安全的惰性单例?

这个实现是否适用于Java中无锁线程安全的惰性单例?,java,multithreading,singleton,lockless,Java,Multithreading,Singleton,Lockless,这就是我到目前为止所拥有的,我的方向正确吗? 其目的是在一个线程比其他线程更频繁地需要访问单例的场景中使用它,因此需要无锁代码,我想使用原子变量进行实践 public final class ThreadSafeLazyCompareAndSwapSingleton { private ThreadSafeLazyCompareAndSwapSingleton(){} private static volatile ThreadSafeLazyCompareAndSwapSingleton

这就是我到目前为止所拥有的,我的方向正确吗? 其目的是在一个线程比其他线程更频繁地需要访问单例的场景中使用它,因此需要无锁代码,我想使用原子变量进行实践

public final class ThreadSafeLazyCompareAndSwapSingleton {

private ThreadSafeLazyCompareAndSwapSingleton(){}

private static volatile ThreadSafeLazyCompareAndSwapSingleton instance;
private final static AtomicBoolean atomicBoolean = new AtomicBoolean(false);

public static ThreadSafeLazyCompareAndSwapSingleton getCASInstance(){
    if (instance==null){
        boolean obs = instance==null;
        while (!atomicBoolean.compareAndSet(true, obs == (instance==null))){
            instance = new ThreadSafeLazyCompareAndSwapSingleton(); 
        }
    }
    return instance;
}

}

呃,这些内联条件/赋值很难理解。这也是一个非常非标准的习语,所以我想知道你是否真的想用它

它至少存在一个问题,即它可以创建多个实例(在您的情况下,这可能是可以接受的,如果您希望无锁,则无法真正避免)。但我认为它也可能返回多个实例,我想这不是您想要的

我不确定是否需要布尔值,也可以直接在
实例
字段上使用AtomicReferenceFieldDupdater

我认为不需要obs,如果可以设置布尔值,则可以创建并返回一个新实例,否则将循环:

if (instance!=null)
  return instance;

if (atomicBoolean.compareAndSet(false, true))
{
  instance = new ThreadSafeLazyCompareAndSwapSingleton();
  return instance;
}
while(instance==null);
return instance;

但是我真的不认为使用这个额外的布尔值是一个好主意。

像这样的东西会更安全-但是有比这个更好的方法

public final class ThreadSafeLazyCompareAndSwapSingleton {

    // THE instance.
    private static volatile ThreadSafeLazyCompareAndSwapSingleton instance;
    // Catches just one thread.
    private final static AtomicBoolean trap = new AtomicBoolean(false);

    public static ThreadSafeLazyCompareAndSwapSingleton getInstance() {
        // All threads will spin on this loop until instance has been created.
        while (instance == null) {
            // ONE of the spinning threads will get past the trap. Probably the first one.
            if ( trap.compareAndSet(false, true)) {
                // By definition of CAS only one thread will get here and construct.
                instance = new ThreadSafeLazyCompareAndSwapSingleton();
            }
        }
        // By definition instance can never be null.
        return instance;
    }

    // Dont let anyone but me construct.
    private ThreadSafeLazyCompareAndSwapSingleton() {
    }

}

请注意,如果在构造过程中引发异常,则此操作将失败。

这里的一个好方法是将线程分为两类:可以实例化类的线程和不能实例化类的线程。(为了简洁起见,我将把类名缩短为Singleton)。然后,您必须考虑每类线程需要做什么:

  • 实例化线程需要存储它们在
    instance
    中创建的引用并返回它
  • 所有其他线程都需要等待,直到设置了
    instance
    ,然后返回它
此外,我们需要确保两件事:

  • 在实例化和所有返回(包括非实例化线程中的返回)之间有一个before边。这是为了线程安全
  • 实例化线程的集合正好有一个元素(当然,假设其中任何一个集合都是非空的)。这是为了确保只有一个实例
好的,这就是我们的四个要求。现在我们可以编写满足它们的代码了

private final AtomicBoolean instantiated = new AtomicBoolean(false);
private static volatile Singleton instance = null;
// volatile ensures the happens-before edge

public static Singleton getInstance() {
    // first things first, let's find out which category this thread is in
    if (instantiated.compareAndSet(false, true) {
        // This is the instantiating thread; the CAS ensures only one thread
        // gets here. Create an instance, store it, and return it.
        Singleton localInstance = new Singleton();
        instance = localInstance;
        return localInstance;
    } else {
        // Non-instantiating thread; wait for there to be an instance, and
        // then return it.
        Singleton localInstance = instance;
        while (localInstance == null) {
            localInstance = instance;
        }
        return localInstance;
    }
}
现在,让我们相信我们的每一个条件都得到了满足:

  • 实例化线程创建一个实例,存储它,然后返回它:这是CAS的“true”块
  • 其他线程等待一个实例被设置,然后返回它:这就是
    循环所做的
  • 在实例化和返回之间有一个before(HB)边:对于实例化线程,在线程语义中确保这一点。对于所有其他线程,
    volatile
    关键字确保写入(在实例化线程中)和读取(在此线程中)之间的HB边缘
  • 假设曾经调用过该方法,则实例化线程集正好是一个大线程。:命中CAS的第一个线程将返回true;所有其他的都将返回false
我们都准备好了


这里的一般建议是将您的需求分解为尽可能具体的子需求。然后,您可以分别处理每一个问题,这更容易推理。

抛开您的实现是否有效不谈,由于它的简单性和效率,我建议您改为使用。您的单例可以多次初始化。这是否编译,我的猜测是否。至于这是否有效,不,它不是。原子最初是假的,唯一的更新预期是真的。@JohnVint我想最初的答案太简单和可靠了,OP正在寻找越来越复杂和不可靠的方法来做同样的事情。;)@JAB-An
enum
要简单得多。顺便说一句,正如其他人所指出的(在评论中也是如此),在Java中有更简单的方法获得无锁单例。如果这是真正的代码,你真的,真的应该使用它们。我的答案是假设这更多的是一个学习多线程的心理练习。trap.compareAndSet(false,true)如何返回true,我是否遗漏了一些重要的内容?@aranhakki-
compareAndSet
如果它成功地将字段从旧值转换为新值,则返回
true
。在本例中,它将原子布尔值设置为
true
,因为它是
false
。这确保只有一个线程进入代码的构造部分。所有其他线程都将循环,直到那个线程完成它的工作。啊,好的,谢谢,我一直在解释,好像预期的匹配更新。因此,当初始值为false时,第一个线程进入并将其设置为true。@aranhakki-正确-我添加了一些注释以澄清。