ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

23种设计模式_创建型模式

23种设计模式_创建型模式 创建型模式的核心使命是优雅地创建对象让对象的创建逻辑与业务逻辑解耦提升系统的灵活性与可维护性。一、工厂方法模式核心思想定义一个创建对象的接口让其子类自己决定将哪一个工厂类实例化工厂模式使创建过程延迟到子类中进行。解耦对象创建与使用逻辑实现方式将对象的创建逻辑放在子类中而不是在客户端代码中。简单工厂VS工厂方法模式代码示例简单工厂// 先抽象产品接口 public interface Transport { void deliver(); } public class Truck implements Transport{ Override public void deliver() { System.out.println(陆地运输卡车配送); } } public class Ship implements Transport{ Override public void deliver() { System.out.println(海运船舶配送); } } // 独立的工厂类简单工厂核心只负责创建对象 public class TransportSimpleFactory { public Transport createTransport(String type) { if (land.equals(type)) { return new Truck(); } else if(sea.equals(type)) { return new Ship(); } return null; } } // 调用业务类只调用工厂不再 new 具体类 class LogisticsManager { public void transportCargo(String type) { TransportSimpleFactory factory new TransportSimpleFactory(); Transport transport factory.createTransport(type); transport.deliver(); } }工厂方法模式// 1抽象产品接口定义所有运输工具的统一行为如 deliver() 方法让所有具体产品实现同一套接口。 // 抽象产品接口所有运输工具必须实现此接口 interface Transport { void deliver(); } // 具体产品实现卡车类 class Truck implements Transport { Override public void deliver() { System.out.println(陆地运输卡车配送); } } // 具体产品实现船舶类 class Ship implements Transport { Override public void deliver() { System.out.println(海运船舶配送); } } // 2抽象工厂类定义工厂方法 createTransport()声明创建产品的接口同时封装稳定的业务逻辑如 planDelivery() 配送流程。 // 抽象工厂类核心工厂方法) abstract class Logistics { // 模板方法组合核心逻辑与工厂方法 public void planDelivery() { Transport transport createTransport(); transport.deliver(); } // 工厂方法声明延迟具体运输工具创建到子类 public abstract Transport createTransport(); } // 3具体工厂子类重写工厂方法返回对应的具体产品实例。 // 具体工厂类陆地物流 class RoadLogistics extends Logistics { Override public Transport createTransport() { // 无需判读条件的直接构造 return new Truck(); } } // 具体工厂类海洋物流 class SeaLogistics extends Logistics { Override public Transport createTransport() { return new Ship(); } } // 客户端调用示例 public class FactoryMethod { public static void main(String[] args) { FactoryMethod client new FactoryMethod(); client.clientMethod(road); } public void clientMethod(String logisticsType) { Logistics logistics; // 通过简单映射选择具体工厂 if (road.equals(logisticsType)) { logistics new RoadLogistics(); } else { logistics new SeaLogistics(); } // 统一调用接口核心业务逻辑无需修改 logistics.planDelivery(); } } // 优化 // 创建对象注册表 class LogisticsRegistry { private static final MapString, SupplierLogistics registry new HashMap(); static { registry.put(road, RoadLogistics::new); // 配置化绑定 可以从配置文件中加载 registry.put(sea, SeaLogistics::new); } public static Logistics getLogistics(String type) { return registry.getOrDefault(type, SeaLogistics::new).get(); } } // 客户端无需任何条件判断 class ClientV2 { public void clientMethod(String logisticsType) { Logistics logistics LogisticsRegistry.getLogistics(logisticsType); logistics.planDelivery(); } }工厂方法模式优点1避免创建者与具体的产品逻辑耦合2满足单一职责每一个业务逻辑实现都在自己所属的类中完成3满足开闭原则无须更改使用调用方就可以在程序中引入新的产品类型。区别总结对比项简单工厂工厂方法模式所属不属于 23 种 GoF 设计模式GoF23 创建型模式之一工厂数量只有 1 个工厂类一个产品对应一个工厂类工厂也形成继承 / 接口体系新增产品修改原有工厂加 if‑else违反开闭原则新增产品类 新增对应的工厂类不修改原有代码满足开闭耦合工厂类职责过重所有产品创建逻辑堆在一起职责拆分每个工厂只负责自己产品缺点工厂类膨胀改一处影响全部产品多后维护灾难条件分支泛滥代码重复风险类数量爆炸产品越多工厂类成倍增加使用场景产品少、变化少的小项目产品种类多未来会频繁扩展新产品通俗理解一个工厂造多个产品。一家加工厂什么东西都生产老板一个人决定造 A 还是造 B。要加新产品就得改这家工厂内部的生产逻辑。一个工厂只造一个产品。开多家专门工厂A 工厂只造 AB 工厂只造 B。需要新产品直接新开一家工厂老工厂代码完全不动。Q1简单工厂为什么不是 23 种设计模式A它没有抽象化工厂只有一个工厂类新增产品要修改工厂内部代码破坏开闭原则只是一种编程习惯。Q2工厂方法能完全消除 if‑else 吗A不能。工厂方法只是消除工厂内部创建产品的 if‑else选择哪一个具体工厂的逻辑依然要放在客户端可以通过 Map 注册器查表消除但这属于业务优化不是模式本身。二、抽象工厂模式抽象工厂也可以称作其他工厂的工厂它可以在抽象工厂中创建出其他工厂与工厂模式一样都是用来解决接口选择的问题。核心思想提供一个创建一系列相关或相互依赖对象的接口而无需指定它们具体的类。实现方式通过定义多个工厂接口每个接口负责创建一组相关的对象。与工厂方法的区别工厂方法关注单一产品一个工厂只生产【一类产品】。抽象工厂处理产品族一个工厂生产【一组 / 一套相关联的系列产品】。代码示例// 1定义抽象产品接口 // 接口约束产品规格 interface Chair { void sit(); } interface Sofa { void lieDown(); } interface CoffeeTable { void placeItem(); } // 2实现具体产品 // 确保现代系列组件统一 class ModernChair implements Chair { Override public void sit() { System.out.println(Modern chair designed seating); } } class ModernSofa implements Sofa { Override public void lieDown() { System.out.println(Modern sofa clean lines design); } } class ModernCoffeeTable implements CoffeeTable { Override public void placeItem() { System.out.println(Modern geometric table surfaces); } } // 保证维多利亚风格一致性 class VictorianChair implements Chair { Override public void sit() { System.out.println(Classic carved wood chair); } } class VictorianSofa implements Sofa { Override public void lieDown() { System.out.println(Antique fabric sofa); } } class VictorianCoffeeTable implements CoffeeTable { Override public void placeItem() { System.out.println(Ornate marble-top table); } } // 3定义抽象工厂接口 interface FurnitureFactory { Chair createChair(); Sofa createSofa(); CoffeeTable createCoffeeTable(); } //4实现具体工厂 // 现代风格产品线工厂 class ModernFactory implements FurnitureFactory { Override public Chair createChair() { return new ModernChair(); } Override public Sofa createSofa() { return new ModernSofa(); } Override public CoffeeTable createCoffeeTable() { return new ModernCoffeeTable(); } } // 维多利亚风格产品线工厂 class VictorianFactory implements FurnitureFactory { Override public Chair createChair() { return new VictorianChair(); } Override public Sofa createSofa() { return new VictorianSofa(); } Override public CoffeeTable createCoffeeTable() { return new VictorianCoffeeTable(); } } // 家具商店 class FurnitureStore { private FurnitureFactory factory; // 动态绑定具体工厂 public FurnitureStore(FurnitureFactory factory) { this.factory factory; } public void showcaseSet() { Chair chair factory.createChair(); Sofa sofa factory.createSofa(); CoffeeTable table factory.createCoffeeTable(); System.out.println(展示完整风格套件:); chair.sit(); sofa.lieDown(); table.placeItem(); } } public class AbstractFactory { public static void main(String[] args) { // 创建现代风格商店 FurnitureStore modernStore new FurnitureStore(new ModernFactory()); modernStore.showcaseSet(); // 创建维多利亚风格商店 FurnitureStore victorianStore new FurnitureStore(new VictorianFactory()); victorianStore.showcaseSet(); } }抽象工厂模式 优缺点核心定位用来创建一组相互关联、相互依赖的产品族保证整套产品配套使用避免出现错误混搭。优点1保证产品族内部产品一致性2高层代码完全面向抽象隔离具体实现3符合开闭原则【对产品族扩展开放】4分离产品创建与业务使用缺点1新增产品种类痛苦最大缺陷。如果要新增产品种类要修改抽象工厂接口全部实现类都要改严重违反开闭原则2类数量膨胀代码复杂度上升。每新增一套产品族就要一次性新增 N 个产品类 1 个具体工厂类产品族一多类数量成倍增加。3结构重理解成本高。相比工厂方法、简单工厂抽象工厂多层抽象业务简单场景过度使用会造成过度设计。三、建造者模式核心思想使用多个简单的对象一步一步构建一个复杂的对象把对象的构建和表示分离。实现方式定义一个建造者接口和具体建造者类通过建造者类来创建复杂对象。代码示例写法1GOF 标准版本完整四角色1Product 产品复杂实体对象由多个成员变量构成// Product类代表复杂对象 class House { private String wall; private String roof; private String floor; // getter setter toString }2Builder 抽象建造者定义构建各个组件的抽象方法//抽象建造者 abstract class HouseBuilder{ protected House house new House(); public abstract void buildWall(); public abstract void buildRoof(); public abstract void buildFloor(); public House getHouse(){ return house; } }3ConcreteBuilder 具体建造者实现组件构建逻辑提供获取最终产品方法//具体建造者普通房子 class CommonHouseBuilder extends HouseBuilder { Override public void buildWall() { house.setWall(砖墙); } Override public void buildRoof() { house.setRoof(普通屋顶); } Override public void buildFloor() { house.setFloor(水泥地面); } } // 具体建造者现代玻璃房屋 class ModernHouseBuilder implements HouseBuilder { Override public void buildWall() { house.setWall(钢化玻璃); } Override public void buildRoof() { house.setRoof(透明生态屋顶); } Override public void buildFloor() { house.setFloor(木质地面); } }4Director导演类统一建造流程调用 builder 的分步方法可选业务代码经常省略业务开发大多省略 Director由客户端自己链式调用。// 导演类控制构建顺序 class Director{ public House build(HouseBuilder builder){ builder.buildWall(); builder.buildFloor(); builder.buildRoof(); return builder.getHouse(); } }5客户端调用换不同 Builder产出不同产品。// 使用 Director director new Director(); House house director.build(new CommonHouseBuilder());写法2Java 简化版日常开发最常用静态内部类 Builder链式调用省略 Director客户端自己链式设置属性。LombokBuilder 底层就是这个思路可以查看 Lombok 编译后 class 反编译代码。public class User { // 私有final不可变对象 private final String name; private final Integer age; private final String phone; private final String address; //私有构造只能由builder来构建 private User(Builder builder){ this.name builder.name; this.age builder.age; this.phone builder.phone; this.address builder.address; } //静态内部建造者 public static class Builder{ private String name; private Integer age; private String phone; private String address; public Builder name(String name){ this.name name; return this; //链式 } public Builder age(Integer age){ this.age age; return this; } public Builder phone(String phone){ this.phone phone; return this; } public Builder address(String address){ this.address address; return this; } //最终构建对象 public User build(){ //可以在这里做参数校验 return new User(this); } } } //调用链式可选参数灵活不用写大量构造器重载 User user new User.Builder() .name(张三) .age(22) .phone(13800000000) .build();Q1建造者模式和工厂模式区别建造者模式工厂模式简单 / 工厂方法 / 抽象工厂侧重分步组装分多步骤构建对象强调构建过程侧重直接产出完整对象一步创建完成对象一般是复杂组合对象由多个零件拼接对象简单、中等都可以不关心组装步骤最后调用build()才返回对象工厂方法直接 return 对象可以精细控制每一步构建逻辑对外屏蔽对象创建细节建造者先造轮子、再造发动机、造车身最后组装成汽车。工厂直接给你一辆完整汽车Q2建造者模式优缺点优点构建与表示解耦相同构建流程可以生成不同产品链式调用可读性高解决多参数构造器重载爆炸不用记参数顺序可以在 build () 方法统一做参数合法性校验方便创建不可变对象成员变量 final构造私有线程安全对象创建过程拆分对创建过程精细化控制。缺点增加代码复杂度每个产品要配套 Builder 类产品字段改动Builder 也要同步修改维护成本上升对象很简单的时候使用属于过度设计。Q3建造者模式适用场景适用对象属性多大量可选参数构造器参数爆炸需要构建不可变对象对象创建过程复杂由多个部件组装需要隔离对象创建逻辑。不适用对象字段很少简单 POJO直接 new set 即可。Q4为什么不用重载构造器要用建造者构造器重载多参数一多分不清参数顺序传错参数编译不会报错很多参数是可选重载会产生大量构造函数Builder 链式调用语义清晰哪个参数设置一目了然build 统一校验参数构造器很难统一校验。Q5建造者和装饰器模式区分建造者创建对象组装零件最终返回新对象属于创建型装饰器包装已有对象动态增强原有对象功能属于结构型Q6SqlSessionFactory、SqlSession、SqlSessionBuilderSqlSessionFactoryBuilder使用了建造者的设计思想但并不是 GoF 标准建造者模式用完就扔只用来生成 SqlSessionFactory用完就可以丢弃。SqlSessionFactory重量级单例全局只创建一份生产 SqlSession。SqlSession会话每次数据库操作新建用完关闭。四、原型模式核心思想主要解决的是创建重复对象的问题。通过复制已有对象原型来创建新对象而不是 new 调用无参构造直接创建对象。实现方式实现Cloneable接口并重写clone()方法来复制对象。代码示例1原型抽象类声明克隆方法clone()abstract class Shape implements Cloneable { protected int xCoord; protected int yCoord; protected String fillColor; // 基础构造方法 public Shape() { this.xCoord 0; this.yCoord 0; this.fillColor #FFFFFF; // 默认填充颜色 } // 原型构造方法拷贝构造核心 // 用来复制父类的公共属性 xCoord、yCoord、fillColor protected Shape(Shape prototype) { this.xCoord prototype.xCoord; // 坐标克隆 this.yCoord prototype.yCoord; // 实现位置复制 this.fillColor prototype.fillColor; // 颜色属性继承 } public void setPosition(int x , int y) { this.xCoord x; this.yCoord y; } public void move(int x , int y) { this.xCoord x; this.yCoord y; } public void setColor(String color) { this.fillColor color; } public abstract Shape clone(); }2具体原型类实现克隆Rectangle、Circleclass Rectangle extends Shape { private int width; private int height; // 拷贝构造方法 public Rectangle(Rectangle source) { super(source); // 调用父类拷贝构造复制父类全部字段 this.width source.width; // 特有属性复制 this.height source.height; // 包括宽高参数 } public Rectangle(int w, int h) { this.width w; this.height h; } // 重写clone() Override public Rectangle clone() { // 返回具体子类类型 return new Rectangle(this); // 原型引导构造 } } class Circle extends Shape { private int radius; public Circle(Circle source) { super(source); // 父类属性初始化 this.radius source.radius; // 半径参数拷贝 } public Circle(int r) { this.radius r; } public void setRadius(int r) { this.radius r; // 半径独立可变 } Override public Circle clone() { return new Circle(this); // 生成新实例 } }3客户端拿到原型对象调用 clone 获取副本public class Prototype { public static void main(String[] args) { Shape prototype new Circle(10); prototype.setPosition(15, 20); prototype.setColor(#FFA500); // 快速生成克隆体 Shape clonedCircle prototype.clone(); // 无需知道具体类型 clonedCircle.move(5, 5); // 获得独立坐标 // 构造矩形原型 Shape rectProto new Rectangle(100,200); Shape clonedRect rectProto.clone(); // 完美复制属性 } }Q1浅拷贝 vs 深拷贝浅拷贝基本类型直接复制值引用类型只复制对象引用地址新旧对象共用同一个引用对象。修改新对象里面引用属性原对象也跟着变。深拷贝基本类型复制引用对象也完整复制一份全新对象新旧对象完全独立互不干扰。代码示例浅拷贝// 实现Cloneable class User implements Cloneable{ private String name; private int age; private Address address; //引用类型 Override public User clone() throws CloneNotSupportedException { return (User) super.clone(); //JVM原生浅拷贝 } //get set } class Address{ String city; }super.clone () 默认是浅拷贝。克隆出来的 User两个对象的 address 指向同一个 Address 对象。修改克隆对象 address.city原对象的 city 也被修改。代码示例深拷贝两种实现方式手动重写 clone把引用对象也 clone 一遍序列化反序列化对象实现 Serializable// 引用对象 class Address implements Serializable { public String city; public String street; public Address(String city, String street) { this.city city; this.street street; } } // 原型对象 class User implements Cloneable, Serializable { public String name; public int age; public Address address; //引用类型浅拷贝的坑点 public User(String name, int age, Address address) { this.name name; this.age age; this.address address; } //方式1手动拷贝构造实现深拷贝 //拷贝构造把原型传进来所有引用对象手动new一份 public User(User source) { this.name source.name; this.age source.age; //重点引用类型不能直接赋值要new新对象 this.address new Address(source.address.city, source.address.street); } //对外暴露克隆方法 public User deepCopyByConstructor(){ return new User(this); } //方式2序列化反序列化实现深拷贝 public User deepCopyBySerialize() throws Exception { //对象写出到字节数组 ByteArrayOutputStream bos new ByteArrayOutputStream(); ObjectOutputStream oos new ObjectOutputStream(bos); oos.writeObject(this); //从字节数组读回对象生成全新副本 ByteArrayInputStream bis new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois new ObjectInputStream(bis); return (User) ois.readObject(); } //JDK原生super.clone() 浅拷贝做对比看bug Override public User clone() throws CloneNotSupportedException { return (User) super.clone(); } } //测试 public class DeepCopyDemo { public static void main(String[] args) throws Exception { Address addr new Address(杭州市,阿里路); User origin new User(张三,30,addr); //1. 浅拷贝演示bug User shallowCopy origin.clone(); shallowCopy.address.city 南京市; System.out.println(origin.address.city); //输出南京市 修改副本原型被篡改这就是浅拷贝问题 //重置回来 origin.address.city 杭州市; //2.手动拷贝构造深拷贝 User deep1 origin.deepCopyByConstructor(); deep1.address.city 苏州市; System.out.println(origin.address.city); //输出杭州市互不干扰 ✔ //3.序列化深拷贝 User deep2 origin.deepCopyBySerialize(); deep2.address.city 宁波市; System.out.println(origin.address.city); //输出杭州市互不干扰 ✔ } }Q2原型模式和 new 的区别new分配内存执行构造方法完成对象初始化基于类模板创建原型 cloneJVM 直接拷贝内存二进制不会执行构造函数基于已有对象实例复制。重点clone 不会调用构造函数克隆出来对象构造器完全不执行。Q3Cloneable 接口里面有什么方法ACloneable 是空标记接口没有任何抽象方法真正 clone 方法定义在Object类是 native 底层方法。如果类没有实现Cloneable调用super.clone()抛出CloneNotSupportedExceptionCloneable的作用给 JVM 一个信号允许该对象做内存拷贝。坑实现 Cloneable但不重写 clone ()外部依然访问不到 Object 的 protected clone ()Q4原型模式属于创建型那它和工厂模式区别工厂模式基于类通过类信息 new 出新对象客户端需要知道类。原型模式基于对象实例复制现有对象生成新对象不需要知道类细节直接复制实例。Q5原型模式违背开闭吗A不违背新增原型只需要实现克隆接口原有客户端代码不用修改。Q6原型模式优缺点优点对象创建性能高规避复杂对象初始化开销客户端不需要关心对象创建细节直接复制原型运行时动态生成对象动态修改原型就可以改变副本行为。缺点深浅拷贝处理麻烦多层嵌套引用深拷贝实现复杂存在循环引用场景时克隆逻辑很难处理每个原型类都要实现克隆逻辑。Q7适用场景适用对象创建代价大比如需要数据库查询、IO 读取才能构建出来的对象需要大批量生成相似对象大部分字段相同少量字段修改需要保护原始原型对象只操作副本不适用对象结构简单直接 new 即可没必要克隆。Q8JDK 哪些地方用到原型模式ArrayList.clone()浅拷贝原型模式。新 list 是新对象但集合内部元素引用不变。HashMap.clone()同样是浅拷贝。注意JDK 集合 clone 全部都是浅拷贝。Q9浅拷贝一定会有问题吗不一定。 如果对象内部没有引用类型全部是基本类型、String浅拷贝完全够用没有副作用。String 是不可变类赋值修改实际是生成新字符串浅拷贝场景下不会出现修改互相影响。五、单例模式核心思想确保一个类只有一个实例并为该实例提供一个全局访问点。实现方式通过私有化构造函数 和 对外提供静态方法来获取唯一实例。私有构造方法防止外部通过new关键字直接创建实例注意阻止不了反射。静态实例引用在类内部持有唯一的实例。全局访问方法通过一个静态方法通常是getInstance()对外提供实例。使用场景工具类、连接池、线程池、配置对象、Spring Bean 默认单例。单例实现方式代码示例1静态类使用public class Singleton_00 { // 可以在第一次运行的时候直接初始化Map类同时也不需要直到延迟加载再使用 public static MapString,String cache new ConcurrentHashMapString, String(); }2懒汉式非线程安全用到的时候才创建实例懒加载多线程环境会产生多个实例线程不安全。public class Singleton { private static Singleton instance; private Singleton(){} public static Singleton getInstance(){ // 多线程同时进入 if 判断会创建多个对象并发下失效。 if(instance null){ instance new Singleton(); } return instance; } }3懒汉 synchronized 加锁线程安全性能差public class Singleton { private static Singleton instance; private Singleton(){} // 每次调用 getInstance 都要竞争锁并发性能差 public static synchronized Singleton getInstance(){ if(instance null){ instance new Singleton(); } return instance; } }4饿汉式静态常量简单线程安全类加载的时候直接创建实例类加载就初始化不管用不用。优点写法简单类加载完成实例化天然线程安全。缺点类加载就创建不用也会占用内存没有懒加载效果。public class Singleton { //类加载直接实例化 private static final Singleton INSTANCE new Singleton(); //私有构造禁止外部new private Singleton(){} public static Singleton getInstance(){ return INSTANCE; } }5静态内部类面试推荐最优写法使用类的静态内部类实现的单例模式既保证了线程安全又保证了懒加载同时不会因为加锁的方式耗费性能JVM 类加载机制保证线程安全不需要手动加锁、不需要 volatile。这主要是因为JVM虚拟机可以保证多线程并发访问的正确性也就是一个类的构造方法在多线程环境下可以被正确的加载缺点无法传入参数构建实例。public class Singleton { private Singleton(){} // 静态内部类只有调用getInstance才会加载InnerHolder实现懒加载 private static class InnerHolder{ private static final Singleton INSTANCE new Singleton(); } public static Singleton getInstance(){ return InnerHolder.INSTANCE; } }6双重检查锁 DCLDouble‑Check Lock业务高频。懒加载线程安全性能较好public class Singleton { //volatile 防止指令重排 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; } }为什么需要 volatilenew Singleton()底层分三步分配对象内存空间对象初始化执行构造器将 instance 引用指向分配的内存JVM 会发生指令重排顺序变成 1‑3‑2。 线程 A 执行完 1、3还没初始化对象线程 B 拿到不为 null 的 instance直接返回拿到一个未初始化完成的对象出现NullPointerException异常。volatile禁止指令重排保证执行顺序。7枚举单例Effective Java 推荐防止反射、序列化破坏单例public enum SingletonEnum { INSTANCE; //业务方法 public void doSomething(){ } } //使用 SingletonEnum.INSTANCE.doSomething();优点天然防止反射攻击、序列化反序列化破坏单例最安全。反射可以暴力调用私有构造器破坏普通单例枚举在 JDK 层面做校验保证只有一个实例反射调用枚举的构造器会直接抛出异常不允许实例化。缺点饿汉式类加载直接初始化。8CAS「AtomicReference」(线程安全)Java并发库提供了很多原子类来支持并发访问的数据安全性如AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference。优势使用CAS不需要使用传统的加锁方式保证线程安全而是依赖于CAS的忙等算法依赖于底层硬件的实现来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销并且可以支持较大的并发性。缺点忙等如果一直没有获取到将会处于死循环中。public class Singleton { private static final AtomicReferenceSingleton INSTANCE new AtomicReferenceSingleton(); private Singleton() { } public static final Singleton getInstance() { for (; ; ) { Singleton instance INSTANCE.get(); if (null ! instance) return instance; INSTANCE.compareAndSet(null, new Singleton()); return INSTANCE.get(); } } public static void main(String[] args) { // 两次输出一样 System.out.println(Singleton.getInstance()); System.out.println(Singleton.getInstance()); } }Q1破坏单例的两种方式反射通过 setAccessible (true)暴力调用私有构造方法创建新对象。枚举可以抵御反射破坏普通 DCL、静态内部类会被反射破坏。序列化与反序列化对象实现 Serializable反序列化会生成全新对象。枚举天然抵御序列化破坏普通单例需要重写readResolve()方法修复。private Object readResolve() { return InnerHolder.INSTANCE; }Q2单例模式优点优点全局只有一份实例节省内存减少对象创建销毁开销全局统一访问入口。缺点职责过重单例同时负责业务逻辑 对象创建违反单一职责不利于单元测试单例硬编码很难 mock多线程环境要处理线程安全容易写出 bug扩展困难。Q3适用场景资源消耗大的对象数据库连接池、线程池全局配置对象工具类只需要一个实例Spring 默认 scopesingleton 就是单例。不适用频繁创建销毁、需要多实例的场景。Q4Spring 单例和 Java 单例区别Java 单例ClassLoader 级别一个类加载器只有一个对象。Spring 单例Spring 容器级别同一个 IOC 容器内一个 Bean 只有一个实例多个容器可以有多份实例。Q5静态内部类单例为什么线程安全外部类加载的时候不会加载静态内部类实现懒加载只有调用getInstance()访问InnerHolder.INSTANCE才会加载内部类JVM 在加载类、初始化 static 变量时底层字节码层面自带锁保证多线程下只初始化一次。不需要手动 synchronized、不需要 volatile。
返回列表