那些面试中常考的设计模式,看这一篇文章就够了!准备面试前可以看一眼!

设计模式通常解决的是#牛客AI配图神器#结构性和行为上的问题,候选人需要通过分析问题并设计合适的方案来解决。面试官可以通过候选人的思路和解决方案来评估其在面对复杂问题时的分析与解决能力。

面试官通过候选人编写的代码来检查代码的清晰度、可读性和简洁性。设计模式本身有着明确的结构,能够反映出候选人在编码时是否注重这些细节,是否能够编写高质量的代码。

设计模式的精髓在于解决实际开发中的常见问题。在面试中,通过手撕设计模式,面试官可以评估候选人对软件开发过程中常见问题(如对象创建、对象之间的通信、系统扩展等)的理解和应对能力。

1. 单例模式(Singleton)

单例模式确保一个类只有一个实例,并提供一个全局访问点。

public class Singleton {
    private static Singleton instance;

    private Singleton() {
        // 私有化构造函数,防止外部创建实例
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

2. 工厂方法模式(Factory Method)

工厂方法模式提供一个创建对象的接口,但由子类决定实例化哪一个类。

abstract class Animal {
    abstract void sound();
}

class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("Woof");
    }
}

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("Meow");
    }
}

abstract class AnimalFactory {
    abstract Animal createAnimal();
}

class DogFactory extends AnimalFactory {
    @Override
    Animal createAnimal() {
        return new Dog();
    }
}

class CatFactory extends AnimalFactory {
    @Override
    Animal createAnimal() {
        return new Cat();
    }
}

3. 观察者模式(Observer)

观察者模式允许一个对象(主题)改变时,所有依赖它的对象(观察者)都会自动收到通知。

import java.util.ArrayList;
import java.util.List;

interface Observer {
    void update(String message);
}

class ConcreteObserver implements Observer {
    private String name;

    public ConcreteObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(String message) {
        System.out.println(name + " received message: " + message);
    }
}

class Subject {
    private List<Observer> observers = new ArrayList<>();

    void addObserver(Observer observer) {
        observers.add(observer);
    }

    void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    void notifyObservers(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}

4. 策略模式(Strategy)

策略模式通过将不同的算法封装成类,让算法可互换,并使得算法的使用者不需要知道算法的具体实现。

interface Strategy {
    void execute();
}

class ConcreteStrategyA implements Strategy {
    @Override
    public void execute() {
        System.out.println("Executing strategy A");
    }
}

class ConcreteStrategyB implements Strategy {
    @Override
    public void execute() {
        System.out.println("Executing strategy B");
    }
}

class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void executeStrategy() {
        strategy.execute();
    }
}

5. 装饰者模式(Decorator)

装饰者模式允许你在不改变对象本身的情况下,动态地扩展其功能。

interface Coffee {
    String getDescription();
    double cost();
}

class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "Simple coffee";
    }

    @Override
    public double cost() {
        return 5.0;
    }
}

abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;

    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    public String getDescription() {
        return coffee.getDescription();
    }

    public double cost() {
        return coffee.cost();
    }
}

class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", with milk";
    }

    @Override
    public double cost() {
        return super.cost() + 1.0;
    }
}

class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", with sugar";
    }

    @Override
    public double cost() {
        return super.cost() + 0.5;
    }
}

6. 适配器模式(Adapter)

适配器模式允许将一个类的接口转换为另一个接口,使得原本不兼容的接口可以一起工作。

interface Target {
    void request();
}

class Adaptee {
    public void specificRequest() {
        System.out.println("Adaptee's specific request");
    }
}

class Adapter implements Target {
    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void request() {
        adaptee.specificRequest();
    }
}

7. 桥接模式(Bridge)

桥接模式通过将实现部分和抽象部分分离,使它们能够独立变化。

abstract class Shape {
    protected DrawingAPI drawingAPI;

    protected Shape(DrawingAPI drawingAPI) {
        this.drawingAPI = drawingAPI;
    }

    public abstract void draw();
}

interface DrawingAPI {
    void drawCircle(double x, double y, double radius);
}

class RedCircle implements DrawingAPI {
    @Override
    public void drawCircle(double x, double y, double radius) {
        System.out.println("Drawing red circle at (" + x + ", " + y + ") with radius " + radius);
    }
}

class Circle extends Shape {
    private double x, y, radius;

    public Circle(double x, double y, double radius, DrawingAPI drawingAPI) {
        super(drawingAPI);
        this.x = x;
        this.y = y;
        this.radius = radius;
    }

    @Override
    public void draw() {
        drawingAPI.drawCircle(x, y, radius);
    }
}

8. 命令模式(Command)

命令模式将请求封装为一个对象,从而使你可以使用不同的请求、队列或日志请求,以及支持可撤销的操作。

interface Command {
    void execute();
}

class Light {
    public void turnOn() {
        System.out.println("Light is ON");
    }

    public void turnOff() {
        System.out.println("Light is OFF");
    }
}

class LightOnCommand implements Command {
    private Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.turnOn();
    }
}

class LightOffCommand implements Command {
    private Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.turnOff();
    }
}

class RemoteControl {
    private Command slot;

    public void setCommand(Command command) {
        slot = command;
    }

    public void pressButton() {
        slot.execute();
    }
}

9. 外观模式(Facade)

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,使得子系统更易使用。

class SubSystem1 {
    public void operation1() {
        System.out.println("Subsystem1 operation1");
    }
}

class SubSystem2 {
    public void operation2() {
        System.out.println("Subsystem2 operation2");
    }
}

class Facade {
    private SubSystem1 subsystem1;
    private SubSystem2 subsystem2;

    public Facade() {
        subsystem1 = new SubSystem1();
        subsystem2 = new SubSystem2();
    }

    public void operation() {
        subsystem1.operation1();
        subsystem2.operation2();
    }
}

10. 状态模式(State)

状态模式允许一个对象在其内部状态改变时改变它的行为,看来外部看这个对象的行为是改变了的。

interface State {
    void handleRequest();
}

class ConcreteStateA implements State {
    @Override
    public void handleRequest() {
        System.out.println("Handling request in State A");
    }
}

class ConcreteStateB implements State {
    @Override
    public void handleRequest() {
        System.out.println("Handling request in State B");
    }
}

class Context {
    private State state;

    public Context(State state) {
        this.state = state;
    }

    public void setState(State state) {
        this.state = state;
    }

    public void request() {
        state.handleRequest();
    }
}

11. 责任链模式(Chain of Responsibility)

责任链模式使多个对象都有机会处理请求,从而避免了请求发送者与接收者之间的耦合关系。

abstract class Handler {
    protected Handler nextHandler;

    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }

    public abstract void handleRequest(int request);
}

class ConcreteHandler1 extends Handler {
    @Override
    public void handleRequest(int request) {
        if (request < 10) {
            System.out.println("Handler1 handled request " + request);
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        }
    }
}

class ConcreteHandler2 extends Handler {
    @Override
    public void handleRequest(int request) {
        if (request >= 10 && request < 20) {
            System.out.println("Handler2 handled request " + request);
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        }
    }
}

12. 享元模式(Flyweight)

享元模式通过共享对象来支持大量细粒度的对象,降低内存使用。

import java.util.HashMap;

interface Flyweight {
    void operation();
}

class ConcreteFlyweight implements Flyweight {
    private String intrinsicState;

    public ConcreteFlyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }

    @Override
    public void operation() {
        System.out.println("Flyweight with state " + intrinsicState);
    }
}

class FlyweightFactory {
    private HashMap<String, Flyweight> flyweights = new HashMap<>();

    public Flyweight getFlyweight(String intrinsicState) {
        if (!flyweights.containsKey(intrinsicState)) {
            flyweights.put(intrinsicState, new ConcreteFlyweight(intrinsicState));
        }
        return flyweights.get(intrinsicState);
    }
}

13. 中介者模式(Mediator)

中介者模式用于将多个对象的通信放在一个中介对象内,从而减少对象之间的直接依赖。

interface Mediator {
    void send(String message, Colleague colleague);
}

abstract class Colleague {
    protected Mediator mediator;

    public Colleague(Mediator mediator) {
        this.mediator = mediator;
    }

    public abstract void receive(String message);
}

class ConcreteMediator implements Mediator {
    private ConcreteColleagueA colleagueA;
    private ConcreteColleagueB colleagueB;

    public ConcreteMediator(ConcreteColleagueA colleagueA, ConcreteColleagueB colleagueB) {
        this.colleagueA = colleagueA;
        this.colleagueB = colleagueB;
    }

    @Override
    public void send(String message, Colleague colleague) {
        if (colleague == colleagueA) {
            colleagueB.receive(message);
        } else {
            colleagueA.receive(message);
        }
    }
}

class ConcreteColleagueA extends Colleague {
    public ConcreteColleagueA(Mediator mediator) {
        super(mediator);
    }

    @Override
    public void receive(String message) {
        System.out.println("Colleague A received: " + message);
    }
}

class ConcreteColleagueB extends Colleague {
    public ConcreteColleagueB(Mediator mediator) {
        super(mediator);
    }

    @Override
    public void receive(String message) {
        System.out.println("Colleague B received: " + message);
    }
}

14.单例模式的多种写法(可以重点看一下单例模式的写法,面试常考!)

【第一种】静态代码 生成单例

注意不要忘了 private HungrySingleton() { } 面试的时候我就忘了就很难受

package singleton.hungry;

public class HungrySingleton {
    private static final HungrySingleton hungrySigleton = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance(){
        return hungrySigleton;
    }
}

【第二种】静态代码块生成单例

在第一种的基础上 把原来的 初始化赋值 改成了 在 static 静态块里面操作

package singleton.hungry;

public class HungryStaticSingleton {
    private static final HungryStaticSingleton hungrySigleton;

    static {
        hungrySigleton = new HungryStaticSingleton();
    }

    private HungryStaticSingleton() {
    }

    public static HungryStaticSingleton getInstance(){
        return hungrySigleton;
    }
}
  • 优点:创建对象时没有加任何的锁、执行效率比较高。
  • 缺点:也很明显,因为其在类加载的时候就初始化了,也就是说不管我们用或者不用都占着空间,如果项目中有大量单例对象,则可能会浪费大量内存空间。

懒汉单例在 修改了 饿汉单例变成了 延时加载 需要的时候才初始化

这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

【第三种】线程不安全单例

如果两个线程同时走到 if(null == lazySingleton) 就会 创建多个实例 就破坏了单例模式

package singleton.lazy;

public class LazySingleton {
    private static LazySingleton lazySingleton = null;

    private LazySingleton() {
    }

    public static LazySingleton getInstance(){
        if(null == lazySingleton){//为空则说明第一次获取单例对象,进行初始化
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;//不为空则说明已经初始化了,直接返回
    }
}

【第四种】synchronized 懒汉单例

在第三种的基础上 加入 public synchronized static LazySyncSingleton getInstance(){ 这样获取单例的时候就避免了 刚才说的那个 线程安全问题 但是让大家都卡在 getInstance 这里 效率也太低了吧 我们 能不能 只 锁 if(null == lazySingleton){ 这里的代码?

package singleton.lazy;

public class LazySyncSingleton {
    private static LazySyncSingleton lazySingleton = null;

    private LazySyncSingleton() {
    }

    public synchronized static LazySyncSingleton getInstance(){
        if(null == lazySingleton){
            lazySingleton = new LazySyncSingleton();
        }
        return lazySingleton;
    }
}

DCL 双锁机制存在的问题

这才是面试官想要看到的代码 必追问的内容 为什么两层 if 判断 null == lazySingleton

package singleton.lazy;

public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton lazySingleton = null;

    private LazyDoubleCheckSingleton() {
    }

    public static LazyDoubleCheckSingleton getInstance(){
        if(null == lazySingleton){
            synchronized (LazyDoubleCheckSingleton.class){
                if(null == lazySingleton){
                    lazySingleton = new LazyDoubleCheckSingleton();
                }
            }
        }
        return lazySingleton;
    }
}

双重检查锁(double-checked locking) 除了两层if语句 还加入了 volatile 关键字 在 private volatile static LazyDoubleCheckSingleton lazySingleton = null; 上面

补充一下 volatile volatile关键字为域变量的访问提供了一种免锁机制, 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新, 因此每次使用该域就要重新计算, 而不是使用寄存器中的值。需要注意的是, volatile不会提供任何原子操作, 它也不能用来修饰final类型的变量。

关键信息提取一下 volatile 免锁 不缓存 不修饰 final 不是原子的 保证数据可见性 避免代码重排

第一使用 volatile 解决多线程下的可见性问题,

  • 因为我们的 getInstance 方法在判断 lazySingleton 是否为 null 时候并没有加锁,
  • 所以假如线程 t1 初始化过了对象,另外线程如 t2 是无法感知的,而加上了 volatile 就可以感知到。

第二把 synchronized 关键字移到了方法内部,尽可能缩小加锁的代码块,提升效率。

我以为已经很厉害了这样 结果还是有 bug 指令重排还存在着

new 对象的顺序

  1. 分配内存来创建对象,即:new。
  2. 创建一个对象 lazySingleton,此时 lazySingleton == null。
  3. 将 new 出来的对象赋值给 lazySingleton。
  • 实际运行的时候为了提升效率,
  • 这 3 步并不会按照实际顺序来运行的。
  • 那我们打个比方,假如有一个线程 t1 进入同步代码块正在创建对象,
  • 而此时执行了上面 3 个步骤中的后面 2 步,
  • 也就是说这时候 lazySingleton 已经不为 null 了,
  • 但是对象却并没有创建结束;
  • 此时又来了一个线程 t2 进入 getInstance 方法,
  • 这时候 if 条件肯定不成了,
  • 线程 t2 会直接返回,
  • 也就相当于返回了一个残缺不全的对象,
  • 这时候代码就会报错了

那还是看看 下面别的 单例模式方法吧

【第五种】内部类懒汉单例

package singleton.lazy;
public class LazyInnerClassSingleton {
    private LazyInnerClassSingleton(){
    }
    public static final LazyInnerClassSingleton getInstance(){
        return InnerLazy.LAZY;
    }
    private static class InnerLazy{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

利用了内部类会等到外部调用时才会被初始化的特性, 用饿汉式单例的思想实现了懒汉式单例

饿汉单例思想 实现 懒汉式单例 感觉太厉害了 前人的智慧总是很厉害的。

【第六种】不允许反射破坏的内部类懒汉单例

前面说的很厉害 但是 Java 的 反射更厉害 单例 模式说破坏就破坏 constructor.setAccessible(true); private 的限制说没就没

package singleton.lazy;

import java.lang.reflect.Constructor;

public class TestLazyInnerClassSingleton {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = LazyInnerClassSingleton.class;
        Constructor constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        Object o1 = constructor.newInstance();
        Object o2 = LazyInnerClassSingleton.getInstance();

        System.out.println(o1 == o2);
    }
}

上有政策下有对策 Java 既是矛又是盾 有枪也有保护罩 既然 破坏单例要 执行构造方法 constructor.newInstance(); 那么我们 先把 构造方法拦截住

package singleton.lazy;

public class LazyInnerClassSingleton {

    private LazyInnerClassSingleton(){
        //防止反射破坏单例
         if(null != InnerLazy.LAZY){
           throw new RuntimeException("不允许通过反射类构造单例对象");
         }
    }
    public static final LazyInnerClassSingleton getInstance(){
        return InnerLazy.LAZY;
    }
    private static class InnerLazy{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

【第七种】不允许序列化破坏的内部类懒汉单例

除了反射会破坏 单例 序列化把java对象 保存到本地 再加载到内存 仍然会破坏单例模式

单例模式 用了 implements Serializable 实现了序列化 也把 破坏单例的 问题带来了

补充知识点 Extends 和 implements Extends 可以理解为全盘继承了父类的功能。implements 可以理解为为这个类附加一些额外的功能;interface 定义一些方法,并没有实现,需要 implements 来实现才可用。extend 可以继承一个接口,但仍是一个接口,也需要 implements 之后才可用。对于 class 而言,Extends 用于(单)继承一个类(class), 而 implements 用于实现一个接口(interface)。

package singleton.lazy;

import java.io.Serializable;

public class LazyInnerClassSingleton implements Serializable {

    private LazyInnerClassSingleton(){
        //防止反射破坏单例
         if(null != InnerLazy.LAZY){
           throw new RuntimeException("不允许通过反射类构造单例对象");
         }
    }

    public static final LazyInnerClassSingleton getInstance(){
        return InnerLazy.LAZY;
    }

    private static class InnerLazy {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

我们用输入输出流来 破坏这个 单例

package singleton.lazy;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class TestLazyInnerClassSingleton2 {
    public static void main(String[] args) {
        //序列化攻击内部类式单例
        LazyInnerClassSingleton s1 = null;
        LazyInnerClassSingleton s2 = LazyInnerClassSingleton.getInstance();

        FileOutputStream fos = null;

        try {
            fos = new FileOutputStream("LazyInnerClassSingleton.text");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("LazyInnerClassSingleton.text");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (LazyInnerClassSingleton)ois.readObject();
            ois.close();

            System.out.println(s1 == s2);//输出:false

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

补充一下流的方法 java.io.ObjectOutputStream.flush() 方法刷新流。这将写入所有缓冲的输出字节,并刷新到基础流。

这个问题 咋解决呢 ?在单例类的代码加入 readResolve() 方法就行

package singleton.lazy;

import java.io.Serializable;

public class LazyInnerClassSingleton implements Serializable {

    private LazyInnerClassSingleton(){
        //防止反射破坏单例
         if(null != InnerLazy.LAZY){
           throw new RuntimeException("不允许通过反射类构造单例对象");
         }
    }

    public static final LazyInnerClassSingleton getInstance(){
        return InnerLazy.LAZY;
    }

    private static class InnerLazy {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }

    private Object readResolve(){
        return InnerLazy.LAZY;
    }
}

JDK 源码中在序列化的时候会检验一个类中是否存在一个 readResolve 方法, 如果存在,则会放弃通过序列化产生的对象,而返回原本的对象。

技术上 没有 银弹 啊

这种方式虽然保证了单例, 但是在校验是否存在 readResolve 方法前还是会产生一个对象, 只不过这个对象会在发现类中存在 readResolve 方法后丢掉, 然后返回原本的单例对象。这种写法只是保证了结果的唯一, 但是过程中依然会被实例化多次, 假如创建对象的频率增大, 就意味着内存分配的开销也随之增大。

注册式单例

spring 的 单例 bean 原来就是这么来的 技术都是 藕断丝连 循环交错的

【第八种】注册式spring容器单例

注册式单例就是将每一个实例都保存起来, 然后在需要使用的时候直接通过唯一的标识获取实例。

  1. private static Map<String,Object> ioc = new ConcurrentHashMap<>();//存储单例对象
  2. 用 getBean 获取 Bean 单例对象 注入 的 代码 obj = Class.forName(className).newInstance(); ioc.put(className,obj);//将className作为唯一标识存入容器
  3. 单例直接从 map 里拿 return ioc.get(className);//如果容器中已经存在了单例对象,则直接返回
package singleton.register;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ContainerSingleton {
    private ContainerSingleton(){
    }

    private static Map<String,Object> ioc = new ConcurrentHashMap<>();//存储单例对象

    public static Object getBean(String className){
        synchronized (ioc){
            if(!ioc.containsKey(className)){//如果容器中不存在当前对象
                Object obj = null;
                try {
                    obj = Class.forName(className).newInstance();
                    ioc.put(className,obj);//将className作为唯一标识存入容器
                }catch (Exception e){
                    e.printStackTrace();
                }
                return obj;
            }
            return ioc.get(className);//如果容器中已经存在了单例对象,则直接返回
        }
    }
}

相关 测试代码 单例类

package singleton.register;

public class MyObject {
}

单元测试

package singleton.register;

public class TestContainerSingleton {
    public static void main(String[] args) {
        MyObject myObject1 = (MyObject) ContainerSingleton.getBean("singleton.register.MyObject");
        MyObject myObject2 = (MyObject) ContainerSingleton.getBean("singleton.register.MyObject");

        System.out.println(myObject1 == myObject2);//输出:true
    }
}

ioc 加入了 synchronized 关键字 如果不加入 默认是线程不安全的

ThreadLocal 式单例

ThreadLocal 式单例不能保证其创建的对象是全局唯一, 但是能保证在单个线程中是唯一的, 在单线程环境下线程天生安全。

【第九种】ThreadLocal线程私有 式单例

获得的单例是 private static final ThreadLocalsingleton

package singleton.thread;

public class ThreadLocalSingleton {
    private ThreadLocalSingleton() {
    }

    private static final ThreadLocal<ThreadLocalSingleton> singleton =
            new ThreadLocal<ThreadLocalSingleton>() {
                @Override
                protected ThreadLocalSingleton initialValue() {
                    return new ThreadLocalSingleton();
                }
            };
    public static ThreadLocalSingleton getInstance(){
        return singleton.get();
    }
}

测试代码

package singleton.thread;

public class TestThreadLocalSingleton {

    public static void main(String[] args) {
        System.out.println(ThreadLocalSingleton.getInstance());//主线程输出
        System.out.println(ThreadLocalSingleton.getInstance());//主线程输出

        Thread t1 = new Thread(()-> {
            ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + ":" + singleton);//t1线程输出
        });
        t1.start();
    }
}

ThreadLocal 式示例仅对单线程是安全的

枚举类 单例

【第十种】JDK保护的枚举类单例

单例类

package singleton.meiju;

public class MyObject {
}

枚举单例

package singleton.meiju;

public enum EnumSingleton {
    INSTANCE;

    private MyObject myObject;

    EnumSingleton() {
        this.myObject = new MyObject();
    }

    public Object getData() {
        return myObject;
    }

    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

用枚举类型 来拿到 这个传统的单例对象

尝试反射破坏

package singleton.meiju;

import java.lang.reflect.Constructor;

public class TestEnumSingleton1 {

    public static void main(String[] args) throws Exception{
        //测试反射是否可以破坏枚举式单例
        Class clazz = EnumSingleton.class;
        Constructor c1 = clazz.getDeclaredConstructor();//无参构造器
        System.out.println(c1.newInstance());
    }
}

编译失败 没有无参构造方法

反编译后的枚举源码 发现无参构造方法是 假的 真实底层 是 string int 两个参数

修改后 反射还是被拒绝了

package DesignPattern.singletion.EnumSingleton;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class TestEnumSingleton1 {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazzz = EnumSingleton.class;
       // Constructor c1 = clazzz.getDeclaredConstructor();
       Constructor c2 = clazzz.getDeclaredConstructor(String.class,int.class);
       c2.setAccessible(true);
       // JDK 底层在保护我们的枚举类不允许被反射创建
      //  System.out.println(c2.newInstance("测试",666));
//        System.out.println(c1.newInstance());

        /*
        @CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }
         */

    }
}

通过查看 反射源码 newInstance 的 JDK 代码 发现 JDK 底层在保护我们的枚举类不允许被反射创建

那再 试试用 序列化破坏这个 枚举单例

package singleton.meiju;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class TestEnumSingleton2 {
    public static void main(String[] args) throws Exception{
        //测试序列化是否可以破坏枚举式单例
        EnumSingleton s1 = null;
        EnumSingleton s2 = EnumSingleton.getInstance();

        FileOutputStream fos = new FileOutputStream("EnumSingleton.text");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s2);
        oos.flush();
        oos.close();

        FileInputStream fis = new FileInputStream("EnumSingleton.text");
        ObjectInputStream ois = new ObjectInputStream(fis);
        s1 = (EnumSingleton)ois.readObject();
        ois.close();
        fis.close();
        System.out.println(s1.getData() == s2.getData());//true
    }
}

序列化也不能破坏我们单例。

全部评论

相关推荐

评论
1
3
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务