单例模式(Singleton):保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例的特点:
- 某个类只能有一个实例;(构造器私有)
- 它必须自行创建这个实例;(自己编写实例化逻辑)
- 它必须自行向整个系统提供这个实例;(对外提供实例化方法)
类图如下:
单例模式分为懒汉式的和饿汉式,有的地方也会讲登记式的单例模式,学习一下这三种单例模式。
饿汉式
public class Person {
// 直接创建一个本类的对象
private static final Person instance = new Person();
// 构造器私有,外部不能实例化
private Person() {
}
public static Person getInstance() {
return instance;
}
}
懒汉式
public class Person {
private static Person instance;
// 构造器私有,外部不能实例化
private Person() {
}
public static Person getInstance() {
// 如果没有实例再去创建
if (instance == null) {
Person person = new Person();
instance = person;
}
return instance;
}
}
多线程下使用懒汉式,会破坏单例模式。
方法加锁
public class Person {
private static Person instance;
// 构造器私有,外部不能实例化
private Person() {
}
// 单例提供给外部的获取方法
// 1. public static synchronized Person getInstance() 锁太大导致效率低下
public static synchronized Person getInstance() {
// 如果没有实例再去创建
if (instance == null) {
Person person = new Person();
instance = person;
}
return instance;
}
}
直接给获取实例的方法加上synchronized
可以结局多线程下的问题,但是此方法会导致效率低下。
双重检查锁 + volatile
private volatile static Person instance;
// 构造器私有,外部不能实例化
private Person() {
}
// 单例提供给外部的获取方法
// 1. 双重检查锁 + 内存可见性
public static synchronized Person getInstance() {
// 如果没有实例再去创建
if (instance == null) {
synchronized (Person.class) {
if (instance == null) {
Person person = new Person();
instance = person;
}
}
}
return instance;
}
使用双重检查锁可以提高效率。使用volatile可以导致指令重排。
防止序列化破坏单例模式
一个单例对象创建好后,有时候需要将对象序列化然后写入磁盘,下次使用时再从磁盘中读取对象并进行反序列化,将其转化为内存对象。反序列化后的对象会重新分配内存,即重新创建,如果序列化的对象目标为单例对象,就违背了单例模式的初衷,相当于破坏了单例。
解决办法:在类中重写readResolve()
方法即可
防止反射破坏单例模式
饿汉式
如果是饿汉式单例模式,直接在构造函数中判断实例是否为null
,代码如下:
public class Person {
private volatile static Person instance = new Person();
// 构造器私有,外部不能实例化
private Person() {
if (instance != null) {
throw new RuntimeException("单例不允许多实例");
}
}
public static Person getInstance() {
return instance;
}
懒汉式
增加一个标志位防止反射破坏
public class Person {
private volatile static Person instance;
private static boolean flag = false;
// 构造器私有,外部不能实例化
private Person() {
synchronized (Person.class) {
if (!flag) {
flag = true;
} else {
throw new RuntimeException("单例不允许多实例");
}
}
}
public static synchronized Person getInstance() {
// 如果没有实例再去创建
if (instance == null) {
synchronized (Person.class) {
if (instance == null) {
Person person = new Person();
instance = person;
}
}
}
return instance;
}
}
其实标志位也不能防止反射破坏单例模式,可以通过枚举类实现单例模式防止反射破化。