设计模式之单例模式
1 单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一 , 它提供了一种创建对象的最佳方式, 该类负责创建自己的对象,同时确保只有单个对象被创建, 提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象.
1 单例的结构
单例模式中主要是以下角色:
- 单例类 只能创建一个实例的类
- 访问类 使用单例类
2 单例的实现
1 饿汉式
1 方式1-静态变量
public class Singleton {
// 私有构造方法
private Singleton() {}
// 在成员位置创建该类的对象
private static Singleton instance = new Singleton();
// 对外提供静态方法获取该对象
public static Singleton getInstance() {
return instance;
}
}
通过成员位置声明Singleton类型的静态变量,且创建类的对象, 该对象随着类的加载和创建. 没有被使用也会被创建,会造成浪费内存.
2 方式2-静态代码块
public class Singleton {
// 私有构造方法
private Singleton() {}
// 在成员位置创建该类的对象
private static Singleton instance;
static {
instance = new Singleton();
}
// 对外提供静态方法获取该对象
public static Singleton getInstance() {
return instance;
}
}
通过成员位置声明Singleton类型的静态变量,而对象则是在静态代码块中创建, 该对象随着类的加载和创建. 没有被使用也会被创建,会造成浪费内存.
2 懒汉式
1 方式1- 线程不安全
public class Singleton {
// 私有构造方法
private Singleton() {}
// 在成员位置创建该类的对象
private static Singleton instance;
// 对外提供静态方法获取该对象
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
在成员位置申明变量,在调用方法时创建类对象,且不为null时,直接返回该对象. 实现按需加载,可节省内存. 但是在多线程环境下,会出现问题.
即线程一,通过判断发现instance为null, 然后切换线程二执行,此时instance依然为null, 所以两个线程获取的对象不一致.
2 方式2-线程安全synchronized
public class Singleton {
// 私有构造方法
private Singleton() {}
// 在成员位置创建该类的对象
private static Singleton instance;
// 对外提供静态方法获取该对象
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
在上述线程不安全的基础上, 直接在方法上添加synchronized锁,可以保证线程安全问题. 但是对该方法加锁,导致执行的效果很低, 后续线程都得等待前面执行完整个方法才执行.
3 方法3-双重检查锁
public class Singleton {
// 私有构造方法
private Singleton() {}
private static Singleton instance;
// 对外提供静态方法获取该对象
public static Singleton getInstance() {
// 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if(instance == null) {
synchronized (Singleton.class) {
// 抢到锁之后再次判断是否为null
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,但在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作. 再加上volatile
关键字,就可解决问题,
volatile
关键字可以保证可见性和有序性,即防止JVM进行指令重排.
public class Singleton {
// 私有构造方法
private Singleton() {}
private static volatile Singleton instance;
// 对外提供静态方法获取该对象
public static Singleton getInstance() {
// 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if(instance == null) {
synchronized (Singleton.class) {
// 抢到锁之后再次判断是否为null
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
4 方式4-静态内部类
public class Singleton {
// 私有构造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
// 对外提供静态方法获取该对象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
第一次加载Singleton类是不会初始化对象, 只有第一次调用getInstance方法,JVM才会加载内部类SingletonHolder,并且初始化内部类静态变量,不仅可以保证线程安全,也保证唯一性.
tips: 静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费.
5 方式5-枚举方式
public enum Singleton {
INSTANCE;
}
枚举类是极力推荐的单例实现模式,属于恶汉式, 因为枚举类型是线程安全的,并且只会装载一次 ,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式.
3 存在的问题
枚举类型不会存在下面两个问题
破坏单例模式:
有两种方式,分别是序列化和反射
1 问题说明
1 序列化反序列化
Singleton类
public class Singleton implements Serializable {
// 私有构造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
// 对外提供静态方法获取该对象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
Test类
public class Test {
public static void main(String[] args) throws Exception {
// 往文件中写对象
// writeObject2File();
// 从文件中读取对象
Singleton s1 = readObjectFromFile();
Singleton s2 = readObjectFromFile();
// 判断两个反序列化后的对象是否是同一个对象
System.out.println(s1 == s2);
}
private static Singleton readObjectFromFile() throws Exception {
// 创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a.txt"));
// 第一个读取Singleton对象
Singleton instance = (Singleton) ois.readObject();
return instance;
}
public static void writeObject2File() throws Exception {
// 获取Singleton类的对象
Singleton instance = Singleton.getInstance();
// 创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));
// 将instance对象写出到文件中
oos.writeObject(instance);
}
}
// 运行结果 false
程序运行的结果是false, 说明破坏单例模式.
2 反射
Singleton类
public class Singleton {
// 私有构造方法
private Singleton() {}
private static volatile Singleton instance;
// 对外提供静态方法获取该对象
public static Singleton getInstance() {
if(instance != null) {
return instance;
}
synchronized (Singleton.class) {
if(instance != null) {
return instance;
}
instance = new Singleton();
return instance;
}
}
}
Test类
public class Test {
public static void main(String[] args) throws Exception {
// 获取Singleton类的字节码对象
Class clazz = Singleton.class;
// 获取Singleton类的私有无参构造方法对象
Constructor constructor = clazz.getDeclaredConstructor();
// 开启暴力反射
constructor.setAccessible(true);
// 创建Singleton类的对象s1
Singleton s1 = (Singleton) constructor.newInstance();
// 创建Singleton类的对象s2
Singleton s2 = (Singleton) constructor.newInstance();
// 判断通过反射创建的两个Singleton对象是否是同一个对象
System.out.println(s1 == s2);
}
}
// 结果 false
程序运行的结果是false, 说明破坏单例模式.
2 问题解决
1 序列化和反序列化问题
在类中添加readResolve()
方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象.
Singleton类
public class Singleton implements Serializable {
// 私有构造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
// 对外提供静态方法获取该对象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* 下面是为了解决序列化反序列化破解单例模式
*/
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
具体原因, 是ObjectInputStream类代码底层,会去调用这个方法,如果存在就调用,否则就返回new出来的对象.
...
private Object readOrdinaryObject(boolean unshared) throws IOException {
...
obj = desc.isInstantiable() ? desc.newInstance() : null;
...
// 在Singleton类中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为true
// hasReadResolveMethod 就是判断是否存在该方法
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
Object rep = desc.invokeReadResolve(obj);
...
}
return obj;
}
...
2 反射问题
public class Singleton {
// 私有构造方法
private Singleton() {
/*
反射破解单例模式需要添加的代码
*/
if(instance != null) {
throw new RuntimeException();
}
}
private static volatile Singleton instance;
// 对外提供静态方法获取该对象
public static Singleton getInstance() {
if(instance != null) {
return instance;
}
synchronized (Singleton.class) {
if(instance != null) {
return instance;
}
instance = new Singleton();
return instance;
}
}
}
通过反射调用时,因为对象已经创建,直接抛出异常,防止通过反射创建对象.
4 JDK源码案例
JDK中Runtime类就是使用的单例设计模式.
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
}