使用原子非阻塞方法的单例与同步

Singleton using Atomic Non Blocking Method vs Synchornized

我尝试使用非阻塞原子布尔值 API 来生成单例对象而不是同步对象。

我有 2 个实现

  1. 通过双重锁定和同步关键字
  2. 通过原子非阻塞调用

我相信我们可以通过 Atomic 实现比同步更健壮和更好的实现。如果我不正确,请提出建议。还执行一些基本的性能测试,这有利于原子实现而不是同步。

支持

// Lazy Initialization
// Thread-safe
// Performance improvement for corresponding calls, once the object is created

public class Singleton {
    private static Singleton instance;

    static final AtomicBoolean isInitialized = new AtomicBoolean(false);

    private Singleton() {
        // private constructor //Thread.sleep(10)

    }

    // 1st Implementation
    // Via Double Locking and Synchronized
    public static Singleton getInstance() {
        if (instance == null) {
            // synchronized block to remove overhead
            synchronized (Singleton.class) {
                if (instance == null) {
                    // if instance is null, initialize
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    // 2nd Implementation + Not ThreadSafe with Null Objects.
    // Via Atomic Non Blocking Method and avoiding Synchronized
    public static Singleton getInstanceViaAtomic() {
        if (instance != null)
            return instance;

        if (isInitialized.compareAndSet(false, true)) {
            return instance = new Singleton();
        }
        return instance;
    }

}

Update @Kayaman 正确识别了上述 impl 不是线程安全的。我在下面修复了。

// 2nd Implementation + Thread Safe
// Via Atomic Non Blocking Method and avoiding Synchronized
public static Singleton getInstanceViaAtomic() throws InterruptedException {
    while(instance == null) {
        if (isInitialized.compareAndSet(false, true)) {
            instance = new Singleton();
        }
    }
    return instance;

}

第二个实现不是线程安全的。显示它的一种简单方法是在 Singleton.

的构造函数中放置一个 Thread.sleep(10);(或更多)

这会导致第一个线程将 isInitialized 设置为 true(而 instance 仍然为空),然后调用 new Singleton();.

另一个线程会将 instance 视为 null,它不会进入 if 块,因为布尔值现在是 true,然后它将 return instance 为空。

所以竞争条件以 NullPointerException 结尾。

如果我们要强制这个解决方案成为一个可行的解决方案,它必须使用自旋锁并且可能是这样的(这是早上的代码,所以如果有什么奇怪的地方请告诉我):

public static Singleton getInstance() {

    // Fast-path when singleton already constructed
    if(instance != null)
        return instance;

    // Spinlock lets the first thread through, others will spin
    while(instance == null && !isInitialized.compareAndSet(false, true))
        ;

    // First thread creates the singleton, spun threads will ignore
    if(instance == null)
        instance = new Singleton();

    return instance;
}

instance 需要 volatile 以确保可见性。

第一个进入的锁将清除自旋锁,因为即使实例为 null,!compareAndSet 也会 return false(因为它是第一次成功)。

在此之后,任何进入的线程都将留在自旋锁中,因为 instance == null!compareAndSettrue。当实例构造完成后,由于第一个条件,自旋锁总是落空。

这基本上是带有自旋锁而不是同步的 DCL,我认为上面的代码没有任何用处。