【问题标题】:Is this Singleton a thread-safe one?这个 Singleton 是线程安全的吗?
【发布时间】:2012-09-19 13:21:02
【问题描述】:

基于this topic,我提出了一个有趣的单例模式版本,它的实现基于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;
        }
    }
}

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

【问题讨论】:

  • 你缺少私有构造函数!
  • 当任何人都可以拨打new StrangeSingleton()时,它怎么可能是单身人士。创建一个不带参数的私有构造函数。
  • 您可以使用AtomicBoolean 代替AtomicInteger。使用compareAndSet()
  • @Santosh 你可以创建任意数量的 StrangeSingleton,只有一个 instance 对象。

标签: java multithreading concurrency singleton atomic


【解决方案1】:

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

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

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

在这种情况下,您不需要这样做,因为 AtomicInteger 包含可变字段,这将确保正确的发生前/发生后行为。


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

enum Singleton {
    INSTANCE;
}

【讨论】:

  • Peter,这与通常的双重检查锁定机制相比如何(从性能的角度来看)?
  • 双重检查锁定比检查两个 AtomicXxxxx 更快。使用 enum 是最快的,因为它不需要 volatile 字段且无需检查。
  • @Santosh 由于某些实现语义,双重锁定在 1.4 及更早版本的所有平台上也不一定安全。 (Wikipedia article.) 这在 5+ 中不是问题。另外,+1
  • @Brian 我已经 8 年没有使用 Java 1.4,所以我忘记了这些事情。事实上,在我已经在使用 Java 5.0 之前,我并不知道这可能是个问题;)
  • 实例在第一次使用之前不会加载。它和其他替代品一样延迟加载。
【解决方案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 函数,则不会。在documentation 中查找happens-before

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-08-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多