文章参考于:
饿汉式
public class Singleton_hungry {
private static Singleton_hungry singleton = new Singleton_hungry();
private Singleton_hungry() {
}
public static synchronized Singleton_hungry getInstance() {
return singleton;
}
}
- 优点:没有加锁,执行效率会提高。
- 缺点:类加载时就初始化,浪费内存。
懒汉式
public class Singleton_lazy {
private static Singleton_lazy singleton;
private Singleton_lazy() {
}
public static synchronized Singleton_lazy getInstance() {
if (null == singleton) {
singleton = new Singleton_lazy();
}
return singleton;
}
}
- 优点:第一次调用才初始化,避免内存浪费
- 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率
双重校验锁
public class Singleton_DCL {
private volatile static Singleton_DCL singleton;
private Singleton_DCL() {
}
public static Singleton_DCL getSingleton() {
if (singleton == null) {
synchronized (Singleton_DCL.class) {
if (singleton == null) {
singleton = new Singleton_DCL();
}
}
}
return singleton;
}
}
- DCL: double-checked locking(双重锁/双重校验锁)
- 优点:线程安全,延迟加载,效率较高
- singleton 采用 volatile 关键字修饰也是很有必要的, singleton = new Singleton_DCL(); 这段代码其实是分
为三步执行:
- 为 singleton 分配内存空间
- 初始化 singleton
- 将 singleton 指向分配的内存地址
- 但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出先问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
- 优点:它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
- 缺点:当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。(在实际工作中,很少使用)