单例模式是什么?
单例模式确保一个类只有一个实例,并提供一个全局访问点。
单例模式解决什么问题?
创建全局变量(类加载才会创建),全局变量也可以保证实例只有一个:
public static int GLOBAL_VARIABLE;
全局变量创建时需要赋值,且无法延迟加载
公有域
将域声明为公有静态常量会破坏封装特性
class Singleton {
public static final Singleton singleton = new Singleton();
private Singleton() {
}
}
懒汉式
创建私有静态变量,私有化构造函数,暴露公有方法返回
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
当两个线程同时进入if (singleton == null)时,就会产生两个实例
懒汉加锁式
给实现1的getInstance()方法加上synchronized,即可解决产生两个实例的问题:
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
加锁降低性能,且只有在第一次执行getInstance()时才需要同步,之后执行应该要直接返回
饿汉式
取消延迟加载,在创建变量时直接初始化,可避免实现1和2的问题,但会提前占用资源
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
}
双重校验锁(推荐)
使用双重检查加锁机制,将变量声明为volatile,首先检查是否为空,为空则对类加锁,加锁后再次检查是否为空,为空则创建
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
可以实现延迟加载和多线程同步,但在某些情况下(JDK1.5之前)也会失效
静态内部类(推荐)
class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.singleton;
}
}
可以实现延迟加载和多线程同步,但(上述所有)可以被反射和反序列化破坏
打破单例及处理
以饿汉式为例
class Singleton implements Serializable {
private static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
}
利用反射
Singleton singleton = Singleton.getInstance();
Class cls = Singleton.class;
try {
Constructor constructor = cls.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton otherSingleton = (Singleton) constructor.newInstance();
System.out.println(singleton);
System.out.println(otherSingleton);
} catch (Exception e) {
e.printStackTrace();
}
如上,通过反射开放构造函数,如下打印的地址不一致
com.demo.demo0.Singleton@ad6e6b0
com.demo.demo0.Singleton@3ea0929
要解决上述问题,可在构造函数中添加判断,当创建第二个实例时抛出异常
class Singleton implements Serializable {
private static Singleton singleton = new Singleton();
private Singleton() {
if (singleton != null) {
throw new RuntimeException();
}
}
public static Singleton getInstance() {
return singleton;
}
}
利用反序列化
Singleton singleton = Singleton.getInstance();
try {
FileOutputStream fileOutputStream = openFileOutput("data", Context.MODE_PRIVATE);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(singleton);
objectOutputStream.flush();
objectOutputStream.close();
FileInputStream fileInputStream = openFileInput("data");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Singleton otherSingleton = (Singleton) objectInputStream.readObject();
System.out.println(singleton);
System.out.println(otherSingleton);
} catch (Exception e) {
e.printStackTrace();
}
如上,利用序列化写到文件,反序列化读取时会产生新的实例,如下打印的地址不一致
com.demo.demo0.Singleton@82694ae
com.demo.demo0.Singleton@64e824f
解决办法是将单例的所有域声明为transient,并提供一个readResolve返回唯一的实例
class Singleton implements Serializable {
private transient static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
private Object readResolve() throws ObjectStreamException {
return singleton;
}
}
枚举(最优)
不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化,也阻止了通过反射调用私有构造方法
public enum Singleton {
INSTANCE;
}