-1

这就是我到目前为止所拥有的,我是否朝着正确的方向前进?目的是在一个线程比其他线程更频繁地需要访问单例的情况下使用它,因此无锁代码是可取的,我想使用原子变量进行练习。

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;
}

}
4

3 回答 3

0

像这样的东西会更安全——但还有比这更好的方法。

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() {
    }

}

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

于 2014-06-26T19:00:28.990 回答
0

这里一个好的思维方法是将线程分为两类:可以实例化类的线程和不能实例化类的线程。(为简洁起见,我将类名缩短为 just Singleton)。那你就得想想每类线程需要做什么:

  • 实例化线程需要存储它们创建的引用instance并返回它
  • 所有其他线程都需要等待,直到instance已经设置,然后返回它

此外,我们需要确保两件事:

  • 在实例化和所有返回(包括非实例化线程中的返回)之间存在先发生边缘。这是为了线程安全。
  • 实例化线程的集合只有一个元素(当然,假设任一集合都是非空的)。这是为了确保只有一个实例。

好的,这就是我们的四个要求。现在我们可以编写满足它们的代码。

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 的“真正”块。
  • 其他线程等待一个实例被设置,然后返回它:这就是while循环所做的。
  • 在实例化和返回之间有一个先发生(HB)边缘:对于实例化线程,线程内语义确保了这一点。对于所有其他线程,volatile关键字确保写入(在实例化线程中)和读取(在此线程中)之间的 HB 边缘
  • 假设该方法被调用过,实例化线程的集合正好是一个大的:第一个命中 CAS 的线程将让它返回 true;所有其他人都将返回false。

所以我们都准备好了。

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

于 2014-06-26T19:00:44.317 回答
0

呃,那些内联条件/赋值很难阅读。它也是非常不标准的成语,所以我想知道你是否真的想使用它。

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

我不确定您是否需要布尔值,您也可以直接在instance现场使用 AtomicReferenceFieldUpdater。

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

if (instance!=null)
  return instance;

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

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

于 2014-06-26T18:51:47.123 回答