普通懒汉式
public class Singleton {
/** 单例对象 */
private static Singleton instance;
/**
* 私有构造方法.
*/
private Singleton() {
}
/**
* 静态方法, 用于获取单利对象.
* 如果单例对象未创建, 则创建新单例对象, 否则直接返回该对象.
*
* @return 单例对象.
*/
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
最简单的懒汉式单例,在首次调用 getInstance(); 时,会对单例对象进行实例化。
然而,这种方式明显无法在多线程模式下正常工作。当线程并发调用getInstance(); 时,由于线程之间没有进行同步,有可能两个线程同时进入 if 条件,导致实例化两次。
线程安全的懒汉式
public class Singleton {
/** 单例对象 */
private static Singleton instance;
/**
* 私有构造方法.
*/
private Singleton() {
}
/**
* 静态方法, 用于获取单利对象.
* 如果单例对象未创建, 则创建新单例对象, 否则直接返回该对象.
*
* @return 单例对象.
*/
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
最简单的线程安全的懒汉模式,通过在 getInstance() 方法上添加 synchronized 关键字,保证同一时间仅有一个线程能够执行该代码段,以保证不会出现上面一种方法产生的问题。
然而,这种方法效率很低。每次调用 getInstance() 方法,都将为代码段加锁,同一时间该代码段只能被一个线程访问。然而除了首次调用外,都是不需要同步的,因为 instance 已经被实例化。
Double-Check
public class Singleton {
/** 单例对象 */
private static Singleton instance;
/**
* 私有构造方法.
*/
private Singleton() {
}
/**
* 静态方法, 用于获取单利对象.
* 如果单例对象未创建, 则创建新单例对象, 否则直接返回该对象.
*
* @return 单例对象.
*/
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Double-check 即双重校验,该方法是针对上述方法提出的一种改进方案。
在 getInstance() 方法中,通过不加锁判断 instance 是否实例化。如果没有实例化,再进行加锁、实例化过程,以减少在实例化后调用 getInstance() 方法导致的性能损耗。
缺陷:instance = new Singleton()语句,看起来是一句代码,但实际上不是一个原子操作。它大致做了三件事:
1)给Singleton的实例分配内存
2)调用构造函数,初始化成员字段
3)将insance对象指向分配的内存空间
但是由于Java编译器允许处理器乱序执行,以及各种其他情况,在JDK1.5前上面的第二步和第三步没有办法保证执行顺序。当执行顺序是1-3-2时,如果执行到3后还没执行2.这时候有另一个线程调用了getInstance()方法,那么它判断到的instance不是null,它不需要经过同步代码块,直接获取到了这个错误的对象去做事去了。这就导致了错误出现。解决方法是private static Singleton instance;改为private static volatile Singleton instance;以及使用JDK1.5以上的版本。建议还是使用静态内部类的方式更好,可以规避上述问题
饿汉式
public class Singleton {
/** 单例对象, 类装载时进行实例化. */
private static final Singleton singleton = new Singleton();
/**
* 私有构造方法.
*/
private Singleton() {
}
/**
* 静态方法, 用于获取单利对象.
*
* @return 单例对象.
*/
public static Singleton getInstance() {
return singleton;
}
}
饿汉式单例的原理是 ClassLoader 装载类是单线程,通过这种机制避免了线程同步问题。
这种方式虽然避免了线程同步问题,但却有可能带来性能问题。
无论该类是否被使用, ClassLoader 都有可能(也有可能被 ClassLoader 忽略)加载该类并实例化该单例对象。所以在基础类库场景下,这种方法会无故消耗更多的资源。
静态内部类方式
public class Singleton {
/**
* 私有构造方法.
*/
private Singleton() {
}
/**
* 静态方法, 用于获取单利对象.
*
* @return 单例对象.
*/
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
/** 单例对象, 类装载时进行实例化. */
private static final Singleton instance = new Singleton();
}
}
这种方法同样利用了 ClassLoader 单线程装载的方式,避免了线程同步问题。然而他和上面一种方法不同的地方在于, instance 对象只有在 SingletonHolder 类被装载的时候才会被实例化。也就是说,只有当 getInstance() 方法调用时,才会被实例化,这样就避免了上述的资源损耗。
枚举方式
public enum Singleton {
INSTANCE;
}