4

基于这个话题,我提出了一个有趣的单例模式版本,它的实现是基于 AtomicIntegers 的。

问题是:

  • 这个实现是否正确且线程安全,通常是否可以使用原子变量进行线程同步和管理?
  • 附加问题:如果这个实现是线程安全的,我真的需要一个volatile实例变量的修饰符吗?
public class StrangeSingleton
{

    private StrangeSingleton() {};

    private static volatile Object instance;

    private static AtomicInteger initCounter = new AtomicInteger();
    private static AtomicInteger readyCounter = new AtomicInteger();

    static Object getInstance()
    {

        if (initCounter.incrementAndGet() == 1)
        {
            instance = new Object();

            readyCounter.incrementAndGet();

            return instance;
        }
        else if (readyCounter.get() == 1)
        {
            return instance;
        }
        else
        {
            //initialization not complete yet.
            //write here some logic you want:               
            //sleep for 5s and try one more time,
            //or throw Exception, or return null..

            return null;
        }
    }
}

更新:添加了私有构造函数,但这不是重点。

4

4 回答 4

8

这个实现是否正确且线程安全,通常可以使用原子变量进行线程同步和管理吗?

它是,但它通常更复杂且 CPU 密集,因为您需要忙于等待以快速响应更改。

附加问题:如果这个实现是线程安全的,我真的需要一个 volatile 修饰符作为实例变量吗?

在这种情况下,您不会因为 AtomicInteger 包含 volatile 字段,这将确保正确的发生前/发生后行为。


当然,您可以只使用线程安全且简单得多的枚举;)

enum Singleton {
    INSTANCE;
}
于 2012-09-19T13:28:17.397 回答
2

这个实现是否正确且线程安全,通常是否可以使用原子变量进行线程同步和管理?

是的,但是对于readyCounter变量,您可能应该使用CountDownLatch,如下所示:

private static AtomicInteger initCounter = new AtomicInteger();
private static CountDownLatch readyCounter = new CountDownLatch(1);

static Object getInstance()
{

    if (initCounter.incrementAndGet() == 1)
    {
        try {
            instance = new Object();
            return instance;
        } finally {
            readyCounter.countDown();
        }
    }
    else
    {
        readyCounter.await();
        return instance;
    }
}

调用await()也解决了初始化竞争条件。(我还添加了一个 try-finally 块以避免在构造函数异常上出现死锁。)

附加问题:如果这个实现是线程安全的,我真的需要一个 volatile 修饰符作为实例变量吗?

不,如果您在访问实例变量之前调用相关AtomicIntegerCountDownLatch函数,则不会。在文档中查找happens-before

于 2012-09-19T13:47:35.607 回答
0

线程T1可能在 上挂起instance = new Object();,然后T2将遇到else{}块,因为readyCounter尚未增加。这不太正确,因为初始化已经完成,落后的是状态簿记

于 2012-09-19T13:48:51.040 回答
0

我宁愿在你的方法中做一个synchronized块。getInstance()这已经足够了。您不需要这些奇怪的计数器,它们也不像@David 注意到的那样安全。

于 2012-09-19T14:10:37.890 回答