设计模式-单例模式


概念

确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例

场景

  • 配置类
  • 需要频繁实例化然后销毁的对象
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象
  • 有状态的工具类对象:计数或者生成序列信息
  • 频繁访问数据库或文件的对象

实现

要素

  1. 私有构造方法 (禁止其他程序创建该类的对象)。
  2. 私有静态引用指向自己实例 (此对象供外部程序使用,要保证唯一,通常定义为instance)。
  3. 以自己实例为返回值的公有静态方法 (通常定义为getInstance()方法)。

1.恶汉模式

public class SingleTon {
//私有静态域,用来存储单利对象
private static SingleTon instance = new SingleTon();
//私有的构造方法,防止其他对象新建
private SingleTon() {}
//共有的静态访问接口,用来获取单例
public static SingleTon getInstance() {
return instance;
}

多线程下是不安全的,而且不是延迟初始化

2.懒汉模式

2.1 单线程模式

public class SingleTon {
//私有静态域,用来存储单例对象
private static SingleTon instance ;
//私有的构造方法,防止其他对象新建
private SingleTon() {}
//共有的静态访问接口,用来获取单利
public static SingleTon getInstance() {
if(Objects.isNull(instance)) {
instance = new SingleTon();
}
return instance;
}
}

2.2  多线程模式

对于单线程不安全的2.1可以在获取对象的时候加锁判断。但是不是每次获取都需要加锁,我们只需要在instance没有实例化,才需要加锁,所以使用双重检测

import java.util.Objects;

public class SingleTon {
//私有静态域,用来存储单例对象
private static SingleTon instance ;
//私有的构造方法,防止其他对象新建
private SingleTon() {}
//共有的静态访问接口,用来获取单例
public static SingleTon getInstance() {
if(Objects.isNull(instance)) {
synchronized(instance){
if(Objects.isNull(instance)) {
instance = new SingleTon();
}
}
instance = new SingleTon();
}
return instance;
}
}

2.2这种是双重检测的实现方式,但是这种方式会遭到反射的破坏

import java.lang.reflect.Constructor;

public class SingleMain {

public static void main(String[] args) throws Exception{
SingleTon origin1 = SingleTon.getInstance();
SingleTon origin2 = SingleTon.getInstance();
SingleTon origin3 = null;
Constructor constructor = SingleTon.class.getDeclaredConstructor();
constructor.setAccessible(true);
origin3 = constructor.newInstance();
System.out.println(origin1==origin2);
System.out.println(origin1==origin3);
}
}

2.3 防止反射破坏

import java.util.Objects;

public class SingleTon {
//私有静态域,用来存储单利对象
private static SingleTon instance ;
private static boolean flag = false;
//私有的构造方法,防止其他对象新建
private SingleTon() {
synchronized(SingleTon.class) {
if(flag==false) {
System.out.println("创建成功");
flag = true;
}else {
throw new RuntimeException("单例遭受攻击");
}
}
}
//共有的静态访问接口,用来获取单利
public static SingleTon getInstance() {
if(Objects.isNull(instance)) {
synchronized(SingleTon.class){
if(Objects.isNull(instance)) {
instance = new SingleTon();
}
}
}
return instance;
}
}

如果实体可以序列化的话,则可能被序列化破坏

首先单例实现序列化接口

public class SingleMain {

public static void main(String[] args) throws Exception{
SingleTon origin1 = SingleTon.getInstance();
SingleTon origin2 = SingleTon.getInstance();
System.out.println(origin1==origin2);
SingleTon origin3 = null;
// Constructor constructor = SingleTon.class.getDeclaredConstructor();
// constructor.setAccessible(true);
// origin3 = constructor.newInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("ser.io")));
oos.writeObject(origin1);

ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("ser.io")));
origin3 = (SingleTon)ois.readObject();
System.out.println(origin1==origin3);
}
}

 由于序列化的时候调用的是readResolve所以这个可以通过实现这个方法避免

import java.io.Serializable;
import java.util.Objects;

public class SingleTon implements Serializable{
private static final long serialVersionUID = 1L;
//私有静态域,用来存储单利对象
private static SingleTon instance ;
private static boolean flag = false;
//私有的构造方法,防止其他对象新建
private SingleTon() {
synchronized(SingleTon.class) {
if(flag==false) {
System.out.println("创建成功");
flag = true;
}else {
throw new RuntimeException("单例遭受攻击");
}
}
}
//共有的静态访问接口,用来获取单利
public static SingleTon getInstance() {
if(Objects.isNull(instance)) {
synchronized(SingleTon.class){
if(Objects.isNull(instance)) {
instance = new SingleTon();
}
}
}
return instance;
}

private Object readResolve(){
return instance;
}
}

除此之外还有使用枚举和静态内部类实现单例模式

双重检测会有一个失效的问题,这个和JVM加载机制有关系可以通过生命instance属性为volatile避免