Singleton中的线程安全性

时间:2010-05-26 11:04:56

标签: java multithreading singleton

据我所知,Java中的双重锁定被破坏了,那么在Java中使单例线程安全的最佳方法是什么?我想到的第一件事就是:

class Singleton{
    private static Singleton instance;

    private Singleton(){}

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

这有用吗?如果是这样,它是最好的方式(我想这取决于具体情况,所以说明特定技术最好的时候会很有用)

5 个答案:

答案 0 :(得分:25)

Josh Bloch建议使用单元素enum类型来实现单例(请参阅 Effective Java 2nd Edition,第3项:使用私有构造函数或枚举类型强制执行单例属性

有些人认为这是一种黑客行为,因为它没有明确传达意图,但确实有效。

以下示例直接来自本书。

public enum Elvis {
   INSTANCE;

   public void leaveTheBuilding() { ... }
}

以下是他的结论:

  

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


enum常数单身保证

JLS 8.9. Enums

  

枚举类型没有除其枚举常量定义的实例之外的实例。尝试显式实例化枚举类型(第15.9.1节)是编译时错误。

     

final clone中的Enum方法确保永远不会克隆枚举常量,并且序列化机制的特殊处理可确保不会因反序列化而创建重复实例。禁止对枚举类型进行反射实例化。总之,这四件事确保在枚举常量定义的范围之外不存在枚举类型的实例。


在延迟初始化

以下代码段:

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常量在第一次访问时不会通过构造函数实例化。

答案 1 :(得分:8)

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

This Wikipedia article提出了另一种方法:

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节。

答案 2 :(得分:5)

我的偏好是:

class Singleton {
    private static final INSTANCE = new Singleton();

    private Singleton(){}

    public Singleton instance(){
        return INSTANCE;
    }
 }

您很少需要延迟初始化。您应始终从急切初始化开始,如果发现问题,只能更改为延迟初始化。除非你已经测量并精确定位了Singleton实例化作为性能问题的罪魁祸首,否则只需使用急切的初始化。它更简单,更高效。

你可以肯定使用枚举,但我个人并不打扰,因为普通急切实例化的好处是安全性(针对反射攻击),而且大多数时候我的代码很容易受到这种攻击:p

答案 3 :(得分:2)

Josh Bloch推荐2种解决方案:

1)恶化:

class Singleton {

   public static Singleton instance = new Singleton();

   ...
}

2)更好:

public enum Singleton {

   INSTANCE;

   ...
}

答案 4 :(得分:2)

您可以使用wiki的这段代码

public class Singleton {
   // Private constructor prevents instantiation from other classes
   private Singleton() {}

   /**
    * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
    * or the first access to SingletonHolder.INSTANCE, not before.
    */
   private static class SingletonHolder { 
     private static final Singleton INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
     return SingletonHolder.INSTANCE;
   }
 }