定义
单例模式(Singleton Pattern):保证一个类仅有一个实例,并提供一个访问它的全局访问点。
通俗理解
每一家公司都会有打印机,公司里面每个员工打印东西的时候,都会使用这一台打印机🖨去进行打印,这样不仅大大节约了成本,而且还可以增加打印机的可维护性。试想一下,如果公司为每一个员工都配备了一个打印机,那么A4纸、墨水、电力都会是一个不小的支出,与此同时的是,如果某个人的打印机坏了,那么还得工作人员去修,那如果坏多几台,那么工作人员将会忙不过来。
单例模式就是这样的一个模式,保证系统里面的某个类只能创建一个实例对象(一家公司只有一台打印机),每个调用方调用的对象都是同一个(所有员工用的都是这台打印机),这不仅仅节约了系统的资源(创建对象消耗内存),而且只要修改一处,就可以使得整个系统生效,不需要修改多处才能够达到目的。
示例
示例将使用打印机当示例。
渣渣程序
打印机
public class Printer {
public void printer() {
System.out.println("打印机打印文档");
}
}
员工,调用方
public class Staff {
public static void main(String[] args) {
Printer printer = new Printer();
printer.printer();
}
}
优化
使用单例模式进行优化。首先,不能让调用方通过new进行实例化,只能是服务提供者提供实例化的方法;然后,提供方实例化时候,无论实例化多少次,都返回同一个对象;最后,调用方只能调用提供方的实例化方法创建对象。
类图
这个方法只涉及到一个类,就不画类图了,直接上程序。
程序
关于单例模式的写法,就像是茴香豆有四种写法一样,单例模式也有n种写法。有懒汉式的,有饿汉式的,有线程安全的,有线程不安全的,有基于内部类的,有基于枚举类的... ...
线程安全 饿汉式 不抗反射 不抗反序列化
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
初始化的时候就创建了,消耗一定的资源,调用的时候直接使用,不需要判断,节约了时间,是一种空间换时间的做法。线程安全,因为类只会被装载一次。不抵抗反射和反序列化(实现了Serializable接口下),可以通过反射和反序列化创建多个实例。
解决
public class Singleton2 implements java.io.Serializable{
private static final Singleton2 instance = new Singleton2();
private Singleton2() {
//解决反射创建实例的问题,反射调用后抛出异常
if (instance != null) {
throw new RuntimeException();
}
}
public static Singleton2 getInstance() {
return instance;
}
//解决反序列化创建实例的问题,从io流读取对象的时候会调用这个方法,readResolve创建的对象会直接替换io流读取的对象
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
非线程安全 懒汉式 不抗反射 不抗反序列化
public class SingletonLazyLoad {
private static SingletonLazyLoad instance;
private SingletonLazyLoad() {
}
public static SingletonLazyLoad getInstance() {
if(instance == null) {
instance = new SingletonLazyLoad();
}
return instance;
}
}
调用的时候才初始化,节约资源。非线程安全。不抗反射,不抗反序列化。抗反射,抗反序列化的解决办法见第一个。
线程安全 懒汉式 不抗反射 不抗反序列化
public class SingletonLazyLoadSyn {
private static SingletonLazyLoadSyn instance;
private SingletonLazyLoadSyn() {
}
public static synchronized SingletonLazyLoadSyn getInstance() {
if(instance == null) {
instance = new SingletonLazyLoadSyn();
}
return instance;
}
}
在获取实例的方法上加上synchronized
关键字,保证在多线程的情况下,只有一个线程能够访问到方法体,保证了线程的安全。没有进入方法的线程会不断尝试去获取锁,而且每个线程进入方法后,需要进行非空的判断才能获取到实例,造成性能消耗。
非线程安全 双重检查锁定 允许重排序 不抗反射 不抗反序列化
public class SingletonLazyDCL {
private static SingletonLazyDCL instance = null;
private SingletonLazyDCL() {}
public static SingletonLazyDCL getInstance() {
if(instance == null) {
synchronized (SingletonLazyDCL.class) {
if(instance == null) {
instance = new SingletonLazyDCL();
}
}
}
return instance;
}
}
JVM的优化,写的程序不一定按照我们写的那样执行下去,一般的创建对象顺序是先分配内存,然后创建对象,最后将这个对象指向相关的内存地址。由于JVM的指令重排序,这个过程可能会变成分配内存,将对象指向相关的内存地址,然后初始化对象。那么如果线程A走完分配内存和将对象指向相关的内存地址,但是没有完成初始化对象,线程B进来了,啊哈,A已经创建了,然后线程B就会出现获取到空实例的情况。
线程安全 双重检查锁定 不允许重排序 不抗反射 不抗反序列化
public class SingletonLazyDCLVolatile {
private volatile static SingletonLazyDCLVolatile instance = null;
private SingletonLazyDCLVolatile() {}
public static SingletonLazyDCLVolatile getInstance() {
if(instance == null) {
synchronized (SingletonLazyDCL.class) {
if(instance == null) {
instance = new SingletonLazyDCLVolatile();
}
}
}
return instance;
}
}
volatile会保证我们的程序运行按照指定的方式去执行,不会出现重排序的情况,要么是null,要么是对象,不会出现地址不为空但是地址所指的对象为null的情况,这在一定的程度上是可以创建一个单例。但是,通过反射,我们还可以在这一个看似完美的程序上,创建多个实例。
静态内部类 线程安全 不抗反射 不抗反序列化
public class SingletonLazyLoadInnerClass {
private static class SingletonHolder {
static final SingletonLazyLoadInnerClass instance = new SingletonLazyLoadInnerClass();
}
private SingletonLazyLoadInnerClass(){}
public static SingletonLazyLoadInnerClass getInstance() {
return SingletonHolder.instance;
}
}
外部类无法访问内部类,只有被调用SingletonLazyLoadInnerClass. getInstance ()
的时候才完成初始化,利用ClassLoader的加载机制来实现难加载,并保证构建单例的线程安全。
枚举 线程安全 抗反射 抗反序列化
public enum SingletonEnum {
INSTANCE;
public void read() {
System.out.println("====read====");
}
}
class Main {
public static void main(String[] args) {
SingletonEnum.INSTANCE.read();
}
}
不可以通过反射创建多个实例,但是使用的是非懒加载,单例对象在枚举类加载的时候就被初始化,可以抵抗反序列化。
通过反射创建对象
public class Ref {
public static void main(String[] args) {
try {
Class<Singleton> single1 = Singleton.class;
Class<Singleton> single2 = Singleton.class;
Constructor<Singleton> c1 = single1.getDeclaredConstructor(null);
Constructor<Singleton> c2 = single2.getDeclaredConstructor(null);
c1.setAccessible(true);
c2.setAccessible(true);
Singleton s1 = c1.newInstance();
Singleton s2 = c2.newInstance();
System.out.println(s1);
System.out.println(s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//com.wusicheng.e15_singleton_pattern.nevv.Singleton@19e1023e
//com.wusicheng.e15_singleton_pattern.nevv.Singleton@7cef4e59
通过反序列化创建对象
public class Ser {
public static void main(String[] args) {
Singleton before = Singleton.getInstance();
try{
FileOutputStream fos = new FileOutputStream("singleton.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(before);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.out"));
Singleton after = (Singleton) ois.readObject();
ois.close();
System.out.println(before);
System.out.println(after);
}catch (Exception e) {
e.printStackTrace();
}
}
}
//com.wusicheng.e15_singleton_pattern.nevv.Singleton@224aed64
//com.wusicheng.e15_singleton_pattern.nevv.Singleton@4e04a765
优点
- 提供方严格管控这个类,严格控制调用方的调用方式
- 只创建一个实例,节省了资源,提高系统性能
缺点
- 没有抽象层,不利于扩展
- 职责过重,违背单一职责原则
- JVM的垃圾回收可能会把单例类回收了,重新调用会重新创建
应用场景
- 只需要一个实例对象的,例如工具类,资源管理类
- 调用方只允许有一个接口进入的
实际例子
JDK的java.lang.System
网友评论