单例模式
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态。
系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
单例模式的特点
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
- 单例模式保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。
单例的四大原则
- 构造私有
- 以静态方法或者枚举返回实例
- 确保实例只有一个,尤其是多线程环境
- 确保反序列化时不会重新构建对象
单例模式的使用场景
- 需要频繁的进行创建和销毁的对象
- 创建对象时耗时过多或耗费资源过多,但又经常用到的对象
- 工具类对象
- 频繁访问数据库或文件的对象
会破坏单例模式的两种情况
实现单例模式的方式
1、饿汉式
在类加载的时候就完成实例化,本身是线程安全的,如果从始至终从未使用过这个实例,则会造成内存的浪费。
1 2 3 4 5 6 7 8 9 10 11 12
| public class Singleton {
private static Singleton instance;
static { instance = new Singleton(); }
public static Singleton getInstance() { return instance; } }
|
2、懒汉式
为了解决饿汉式可能造成的内存浪费问题,希望实例可以在使用的时候再被加载,即实现延迟加载,但是需要考虑到线程安全的问题
加上 synchronized 同步锁,实例化代码执行一次后,后面再次访问时,判断 if (instance== null),直接 return 实例化对象以提高执行效率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Singleton {
private static volatile Singleton instance;
private Singleton() { }
public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
|
使用静态内部类,静态内部类只有在调用时才会加载,它保证了 Singleton 实例的延迟初始化,又保证了实例的唯一性。它把 singleton 的实例化操作放到一个静态内部类中,在第一次调用 getInstance() 方法时,JVM 才会去加载 SingletonInstance 类,同时初始化 singleton 实例,所以能让 getInstance() 方法线程安全。
静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton {
private Singleton() { }
private static class SingletonInstance { private static final Singleton instance = new Singleton(); }
public static Singleton getInstance() { return SingletonInstance.instance; } }
|
通过枚举实现
通过枚举实现不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象
但是不是延迟加载的,并且很多框架都是使用反射进行构造实例的
1 2 3 4 5 6 7 8 9 10 11
| public enum SingletonEnum { INSTANCE;
public void sayHello() { System.out.println("你好"); } }
|
测试序列化和反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public class SingletonStaticInnerSerializeTest {
public static void main(String[] args) { try { Singleton instance = Singleton.getInstance(); System.out.println(instance.hashCode());
FileOutputStream fo = new FileOutputStream("test"); ObjectOutputStream oo = new ObjectOutputStream(fo); oo.writeObject(instance); oo.close(); fo.close();
FileInputStream fi = new FileInputStream("test"); ObjectInputStream oi = new ObjectInputStream(fi); Singleton serialize = (Singleton) oi.readObject(); oi.close(); fi.close(); System.out.println(serialize.hashCode()); } catch (Exception e) { e.printStackTrace(); } } }
class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private Singleton() { }
private static class SingletonInstance { private static final Singleton instance = new Singleton(); }
public static Singleton getInstance() { return SingletonInstance.instance; } }
|
结果
解决方法
在反序列化中使用 readResolve()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private Singleton() { }
private static class SingletonInstance { private static final Singleton instance = new Singleton(); }
public static Singleton getInstance() { return SingletonInstance.instance; }
private Object readResolve() { System.out.println("调用了readResolve方法"); return SingletonInstance.instance; } }
|
结果
1 2 3
| 1735600054 调用了readResolve方法 1735600054
|