单例中的螺纹安全性

2022-09-03 17:00:03

我知道Java中的双锁被打破了,那么在Java中使单例线程安全的最佳方法是什么?我脑海中浮现的第一件事是:

class Singleton{
    private static Singleton instance;

    private Singleton(){}

    public static synchronized Singleton getInstance(){
        if(instance == null) instance = new Singleton();
        return instance;
    }
}

这有效吗?如果是这样,这是最好的方法吗(我想这取决于具体情况,所以说明什么时候特定技术是最好的,将是有用的)


答案 1

Josh Bloch 建议使用单元素类型来实现单例(请参阅有效的 Java 第 2 版,第 3 项:使用私有构造函数或枚举类型强制实施单例属性)。enum

有些人认为这是一个黑客,因为它没有清楚地传达意图,但它确实有效。

以下示例直接取自本书。

public enum Elvis {
   INSTANCE;

   public void leaveTheBuilding() { ... }
}

以下是他的结束语:

这种方法[...]更简洁,免费提供序列化机制,并提供针对多个实例化的铁定保证,即使面对复杂的序列化或反射攻击也是如此。虽然这种方法尚未被广泛采用,但单元素枚举类型是实现单例的最佳方式


关于恒定单例保证enum

JLS 8.9.枚举

枚举类型除了由其枚举常量定义的实例外,没有其他实例。尝试显式实例化枚举类型 (§15.9.1) 是编译时错误。

中的方法确保永远不会克隆枚举常量,序列化机制的特殊处理可确保永远不会由于反序列化而创建重复的实例。禁止枚举类型的反射实例化。总之,这四件事可确保除了枚举常量定义的实例之外,不存在枚举类型的实例。final cloneEnum


关于延迟初始化

以下代码段:

public class LazyElvis {
    enum Elvis {
        THE_ONE;
        Elvis() {
            System.out.println("I'M STILL ALIVE!!!");
        }       
    }
    public static void main(String[] args) {
        System.out.println("La-dee-daaa...");
        System.out.println(Elvis.THE_ONE);
    }
}

生成以下输出:

La-dee-daaa...
I'M STILL ALIVE!!!
THE_ONE

如您所见,常量在首次访问之前不会通过构造函数实例化。THE_ONE


答案 2

我认为您的实现没有问题(除了由于其他原因,单例监视器的锁可能被其他方法使用,因此,不必要地阻止其他线程获取实例)。这可以通过引入额外的锁定来避免。Object lock

这篇维基百科文章提出了另一种方法:

public class Something {
    private Something() {
    }

    private static class LazyHolder {
        private static final Something INSTANCE = new Something();
    }

    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }
}

从文章中:

此实现是性能良好且在所有版本的 Java 中都有效的并发实现。
...
该实现依赖于Java虚拟机(JVM)中明确指定的执行初始化阶段;有关详细信息,请参阅 Java 语言规范 (JLS) 的第 12.4 节。


推荐