基本概念
- 定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例
- 违背单一职责原则:既负责实现对象逻辑,由负责生成管理对象。
分类
饿汉式
饿汉式:类加载到内存后,就会实例化。JVM线程安全。缺点:不论是否需要使用,都会加载
public class Mgr01 {
private static final Mgr01 INSTANCE = new Mgr01();
//私有构造器,无法使用new来创造实例
private Mgr01(){
}
private static Mgr01 getInstance(){
return INSTANCE;
}
}
public class Mgr02 {
private static final Mgr02 INSTANCE;
//利用静态代码块来初始化对象
static {
INSTANCE = new Mgr02();
}
private Mgr02(){
}
private static Mgr02 getInstance(){
return INSTANCE;
}
}
懒汉式
- lazy loading 懒加载,只有使用的时候才创造实例
方法加互斥锁
不推荐!调用该方法时都要判断是否获取锁
private static Mgr04 INSTANCE;
private Mgr04(){
}
private synchronized static Mgr04 getInstance(){
if(INSTANCE != null){
INSTANCE = new Mgr04();
}
return INSTANCE;
}
}
dcl(double check lock)
public class Mgr03 {
//volatile保重操作的有序性
private volatile static Mgr03 INSTANCE;
private Mgr03(){
}
//双重检查
/**
* 当某个线程通过第一个if判断后还没有获取锁,另一个线程先获取锁new了对象,如果没有第二个if判断就会出现对象不同的情况
* @return
*/
private static Mgr03 getInstance(){
if(INSTANCE != null){
//1
synchronized (Mgr03.class){
if(INSTANCE != null) {
INSTANCE = new Mgr03();
//执行了三步1. 分配内存空间2,初始化对象3,设置instance指向刚分配的内存地址
//在一些JIT编译器 2,3两步可能会被重排,即先分配地址后初始化对象
//当一个线程分配完地址后,另一个线程判断第一个if就为ture,这时候返回一个未初始化对象的地址
//改进1.volatile(不允许重排)2.允许重排(mgr05)
}
}
}
return INSTANCE;
}
静态内部类(推荐)
/**
* @description: 静态内部类的写法,jvm保重单例,加载外部类的时候不会加载内部类,实现懒加载(内部类只有使用的时候才加载)
* @author: liSen
* @time: 2021/7/27 23:16
*/
public class InnerStaticSingleton{
/**
* 静态初始化器,JVM保证线程安全
*/
private static class SingletonHolder {
private static InnerStaticSingleton instance = new InnerStaticSingleton();
}
/**
* 私有构造函数
*/
private InnerStaticSingleton(){
}
/**
* 获取对象实例方法
* @return
*/
public static InnerStaticSingleton getInstance(){
return SingletonHolder.instance;
}
}
枚举
/**
* 不但解决线程安全,防止反序列化(枚举类没有公共构造方法)
*/
public enum EnumSingleton {
/**
* 实例对象
*/
INSTANCE;
private Object data;
/**
* 私有构造器实现初始化逻辑
*/
private EnumSingleton(){
System.out.println("hhhh");
}
public Object getData(){
return data;
}
public void setData(Object data){
this.data = data;
}
public static EnumSingleton getInstance(){return INSTANCE;}
}
扩展
指定产生实例的个数
public class Emperor {
private static final int maxNum = 2; //控制产生实例的个数
private static ArrayList<Emperor> emperors = new ArrayList<>();
private String emperorName;//皇帝姓名,不能为static,当为static的时候多个emperor共享同一属性
static {//初始化实例
for (int i = 0; i < maxNum; i++) {
emperors.add(new Emperor("皇帝"+ i));
}
}
private Emperor(){
};
private Emperor(String name){
emperorName = name;
};
public static Emperor getInstance(int num){
return emperors.get(num);
}
public void say(){
System.out.println(emperorName);
}
}
public class Minister {
public static void main(String[] args) {
//定义5个大臣
int ministerNum =5;
for(int i=0;i<ministerNum;i++){
Emperor emperor = Emperor.getInstance(new Random().nextInt(2));
System.out.print("第"+(i+1)+"个大臣参拜的是:");
emperor.say();
}
}
}
单例对象的攻击方式
反射
利用jdk反射ApI,修改单例构造函数的访问权限,调用构造函数。
//反射破坏访问
//获取Class类
Class clazz = InnerStaticSingleton.class;
//返回类的空参构造函数
Constructor constructor = clazz.getDeclaredConstructor();
//暴力访问,调用的时候不检查访问权限
constructor.setAccessible(true);
//生成实例对象
InnerStaticSingleton innerStaticSingleton = (InnerStaticSingleton) constructor.newInstance();
//@4554617c
System.out.println(innerStaticSingleton);
序列化
将单例对象实例以字节流的方式写入文件中,然后再读取字节流,反序列化生成对象实例。
//序列化破坏(原始类要实现Serializable接口)
FileOutputStream outputStream = new FileOutputStream("a.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(instance);
objectOutputStream.flush();
objectOutputStream.close();
FileInputStream inputStream = new FileInputStream("a.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
InnerStaticSingleton instance3 = (InnerStaticSingleton) objectInputStream.readObject();
//@27bc2616
System.out.println(instance3);
调用对象clone方法
//clone攻击(原始类要实现Cloneable接口并实现clone方法
InnerStaticSingleton cloneInstance = (InnerStaticSingleton) instance.clone();
//@3941a79c
System.out.println(cloneInstance);
是否考虑
如果你的单例类作为工具类库提供给别人使用就要考虑,如果自己使用一般不需要。
修复
反射
/**
* 私有构造函数:只要调用的时候不为null说明是非法调用
*/
private InnerStaticSingleton(){
//修复反射bug
if(SingletonHolder.instance != null){
throw new RuntimeException();
}
}
序列化
/**
* 防止序列化破坏(readObject的时候如果类重写了该方法就调用该方法的内容)
* @return
*/
public Object readResolve(){
return getInstance();
}
克隆
/**
* 重写clone方法,直接返回创建好的对象
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return getInstance();
}