一、概述
单例模式是在开发中经常使用到的设计模式,它能保证在JVM中只有一个实例存在。下面来看几种单例模式的写法。
二、饿汉模式
//饿汉模式
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
/**
* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
*
* @return
*/
public Object readResolve() {
return getInstance();
}
在加载类的时候就创建了一个对象,以空间换时间,并且是线程安全的。
三、懒汉模式
//懒汉模式
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
/**
* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
*
* @return
*/
public Object readResolve() {
return getInstance();
}
在加载类的时候不会去创建对象,而是在使用时再去创建,以时间换空间,在多线程下是不安全的。
四、双重检查
//双重检查
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
/**
* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
*
* @return
*/
public Object readResolve() {
return getInstance();
}
在对象被使用时才创建,第一次判空避免非必要加锁,当第一次加载时才会加锁再实例化,这样既能节省空间,又能保证线程安全。但是,JVM存在乱序执行功能,也会出现线程不安全的情况。
什么是乱序执行?看这行代码是怎么在JVM里面执行的,instance = new Singleton();
正常执行分3步:
1、在堆内存开辟内存空间
2、在堆内存实例化Singleton各个参数
3、赋值给instance
由于JVM存在乱序执行,可能还没执行完第2步就先执行了第3步,这样另外一个线程判断instance非空,就直接拿来使用,导致异常。
在jdk 1.6之后,只要在定义时加上volatile关键字就可以解决这个问题,即:private volatile static Singleton instance = null;
volatile确保instance每次都在主存中读取,这样做会使执行效率更慢,但可以保证线程安全。
五、静态内部类
//静态内部类实现单例
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
*
* @return
*/
public Object readResolve() {
return getInstance();
}
使用静态内部类的好处是:当外部类加载时,不会立即加载内部类,这样instance不会被初始化,减小内存占用。
当调用getInstance时才会被初始化,而且只会new一次,这样既能保证线程安全,同时又延迟了对象的实例化。
所以推荐使用静态内部类来写单例。