单例模式
- 一个类在整个应用程序中只存在一个实例对象,单例模式就是为这个唯一的实例对象提供访问入口
- 优点:1.内存开销小,只创建一个实例对象。 2.避免资源冲突,像连接池,线程池避免多个实例同时操作一个资源导致混乱。 3.全局只有一个访问入口,数据逻辑状态统一
- 缺点:1.
饿汉式
- 饿汉就是迫不及待想创建对象,在加载阶段就创建好的,不管后面有没有使用这个对象都会创建,但是Java类加载机制的天然线程安全的,一个类的JVM只会初始化一次
- 静态常量类型
// 饿汉式-静态常量版
public class HungrySingleton {
// 1. 私有化构造方法,禁止外部new创建
private HungrySingleton() {}
// 2. 类内部提前创建好唯一的实例(静态常量)
private final static HungrySingleton INSTANCE = new HungrySingleton();
// 3. 提供全局访问入口,返回唯一实例
public static HungrySingleton getInstance() {
return INSTANCE;
}
}
- 静态代码块 : 如果在初始化的时候需要配置问价或者相关的参数就可以使用静态代码块,适合更复杂的逻辑
// 饿汉式-静态代码块版
public class HungrySingleton2 {
// 1. 私有化构造方法
private HungrySingleton2() {}
// 2. 声明静态实例变量,不直接赋值
private static HungrySingleton2 INSTANCE;
// 3. 静态代码块中初始化实例
static {
INSTANCE = new HungrySingleton2();
}
// 4. 全局访问入口
public static HungrySingleton2 getInstance() {
return INSTANCE;
}
}
懒汉式
- 懒汉式是需要使用的时候才开始创建对象,但是这样为了保证线程安全需要给整个getInstance方法加上synchronized方法,这样虽然可以保证懒加载和线程安全,但是性能会下降很多
// 懒汉式-线程安全版(同步方法)
public class LazySingletonSafe {
// 1. 私有化构造方法
private LazySingletonSafe() {}
// 2. 声明静态实例变量
private static LazySingletonSafe INSTANCE;
// 3. 全局访问入口:加 synchronized 修饰整个方法
public static synchronized LazySingletonSafe getInstance() {
if (INSTANCE == null) {
INSTANCE = new LazySingletonSafe();
}
return INSTANCE;
}
}
双层校验锁(DCL)
- 减少sychronized的锁粒度,锁住代码块,第一层检查如果实例存在就直接返回实例,无锁速度快。第二层非空检查在多线程环境下,如果A线程先拿到锁创建了实例,B排队拿到锁之后发现已经有了实例就不需要再创建实例了
- 静态变量实例声明的时候一定要加volatile关键字禁止指令重排,在第二层实例对象创建的时候如果A在创建对象的时候进行了指令重排,创建的对象只有一个空壳子没有初始化,那么B拿到的实例就是个空壳子就返回了。
// 双重检查锁 DCL 版(开发中最优写法之一)
public class DCLSingleton {
// 1. 私有化构造方法
private DCLSingleton() {}
// 2. 声明静态实例变量,必须加 volatile 关键字!!!
private static volatile DCLSingleton INSTANCE;
// 3. 全局访问入口
public static DCLSingleton getInstance() {
// 第一次检查:判断实例是否存在,不存在才加锁,提高并发效率
if (INSTANCE == null) {
// 加锁:只对「实例创建」的代码块加锁,缩小锁粒度
synchronized (DCLSingleton.class) {
// 第二次检查:拿到锁之后,再次判断实例是否存在
if (INSTANCE == null) {
INSTANCE = new DCLSingleton();
}
}
}
return INSTANCE;
}
}
枚举单例
- JVM保证枚举类也只加载一次,天然线程安全
- 天然懒加载,在第一次被访问的时候才会被初始化
- 绝对防止反射,反序列化破坏单例
// 枚举单例 (最优终极写法,无任何缺陷)
public enum EnumSingleton {
// 1. 定义一个枚举常量,就是这个类的唯一实例
INSTANCE;
// 自定义业务方法(按需添加)
public void doBusiness() {
System.out.println("枚举单例的业务逻辑");
}
}
// 调用方式(全局唯一入口)
EnumSingleton.INSTANCE.doBusiness();
破坏单例模式
反射破坏单例
// 1. 通过DCL的正规方法获取单例实例
DCLSingleton instance1 = DCLSingleton.getInstance();
// 2. 通过反射,暴力获取DCL的【私有构造方法】
Class<DCLSingleton> clazz = DCLSingleton.class;
Constructor<DCLSingleton> constructor = clazz.getDeclaredConstructor();
// 核心代码:暴力解除访问权限检查!!!
constructor.setAccessible(true);
// 3. 通过反射调用私有构造,创建新的实例
DCLSingleton instance2 = constructor.newInstance();
- 改造 DCL 的私有构造方法,加「实例存在校验」,如果是在先调用getInstance()方法创建了实例,这种校验可以防止反射破坏,但是如果反射先出手,在调用getInstance()之前,就通过反射创建实例,此时INSTANCE还是null,构造方法的校验逻辑失效,照样能创建多个实例!
private DCLSingleton() {
// ========== 新增防反射校验 ==========
if (INSTANCE != null) {
throw new RuntimeException("禁止通过反射创建实例!破坏单例!");
}
}
反序列化破坏单例
- 直接通过Unsafe.allocateInstance(Class clazz),在堆内存上直接分配实例对象,直接绕开了构造方法
建造者模式
- 核心问题是解决复杂对象创建的问题。使用传统的构造方法,如果对象非常复杂,就需要多个构造函数。对象创造逻辑混乱,如果把对象的一些属性和对象创建耦合起来,后面想要增加新属性需要大量的修改原有代码
- 4个核心角色:产品,抽象建造者,具体建造者,指挥者
1. 产品类(Product):Computer(要创建的复杂对象),就是一个普通的对象类
2. 抽象建造者(Builder):ComputerBuilder(生产规范)
// 抽象建造者:电脑生产规范
public abstract class ComputerBuilder {
// 定义各部分的构建方法(抽象,由具体生产线实现)
public abstract void buildCpu();
public abstract void buildMemory();
public abstract void buildHardDisk();
public abstract void buildGraphicsCard();
public abstract void buildKeyboardMouse();
// 获取最终构建的电脑产品
public abstract Computer getComputer();
3. 具体建造者(ConcreteBuilder)继承之前的抽象类:
① 办公电脑建造者(OfficeComputerBuilder)
// 具体建造者1:办公电脑生产线
public class OfficeComputerBuilder extends ComputerBuilder {
// 持有产品对象,逐步组装
private Computer computer = new Computer();
@Override
public void buildCpu() {
computer.setCpu("酷睿i3(办公专用)");
}
@Override
public void buildMemory() {
computer.setMemory("8G DDR4");
}
@Override
public void buildHardDisk() {
computer.setHardDisk("256G 固态硬盘");
}
@Override
public void buildGraphicsCard() {
computer.setGraphicsCard("核显(满足办公)");
}
@Override
public void buildKeyboardMouse() {
computer.setKeyboardMouse("普通有线键鼠(可选)");
}
@Override
public Computer getComputer() {
return this.computer;
}
}
4. 指挥者(Director):ComputerDirector(车间主任)
// 指挥者:车间主任(控制构建顺序)
public class ComputerDirector {
// 构建电脑的核心方法:传入具体生产线,按固定顺序组装
public Computer constructComputer(ComputerBuilder builder) {
// 固定构建顺序:核心配件→可选配件(顺序可灵活修改)
builder.buildCpu();
builder.buildMemory();
builder.buildHardDisk();
builder.buildGraphicsCard();
builder.buildKeyboardMouse();
// 返回组装好的电脑
return builder.getComputer();
}
}
}