北京JAVA基础面试30天打卡04

发布于:2025-08-08 ⋅ 阅读:(18) ⋅ 点赞:(0)

1. 单例模式的实现方式及线程安全

单例模式(Singleton Pattern)确保一个类只有一个实例,并提供一个全局访问点。以下是常见的单例模式实现方式,以及如何保证线程安全:

单例模式的实现方式
  1. 饿汉式(Eager Initialization)

    • 实现:在类加载时就创建实例(静态初始化)。

    • 代码示例

      public class Singleton {
      
          private static final Singleton instance = new Singleton();
          private Singleton() {}
          public static Singleton getInstance() {
              return instance;
          }
      
      }
      
    • 线程安全:天生线程安全,因为实例在类加载时创建,JVM保证类加载过程是线程安全的。

    • 优缺点:简单,但可能会导致资源浪费(如果实例未被使用)。

  2. 懒汉式(Lazy Initialization)

    • 实现:在第一次调用时创建实例。

    • 代码示例(非线程安全)

      public class Singleton {
      
          private static Singleton instance;
          private Singleton() {}
          public static Singleton getInstance() {
              if (instance == null) {
                  instance = new Singleton();
              }
             return instance;
          }
      
      }
      
    • 线程安全问题:多线程环境下,可能多个线程同时判断instance == null,导致多次创建实例。

    • 改进(加锁)

      public class Singleton {
      
          private static Singleton instance;
          private Singleton() {}
          public static synchronized Singleton getInstance() {
              if (instance == null) {
                  instance = new Singleton();
              }
              return instance;
          }
      }
      
      • 使用synchronized关键字保证线程安全,但锁粒度较大,性能较低。
  3. 双重检查锁(Double-Checked Locking)

    • 实现:在懒汉式基础上优化,使用双重检查和volatile关键字。

    • 代码示例

      收起自动换行

      public class Singleton {
      
          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;
          }
      }
      
    • 线程安全:volatile防止指令重排序,确保实例初始化完成前其他线程不会访问;双重检查减少锁的开销。

    • 优缺点:性能较高,但代码稍复杂。

  4. 静态内部类(Static Inner Class)

    • 实现:利用静态内部类的延迟加载特性。

    • 代码示例

      public class Singleton {
      
          private Singleton() {}
          private static class SingletonHolder {
              private static final Singleton INSTANCE = new Singleton();
          }
          public static Singleton getInstance() {
              return SingletonHolder.INSTANCE;
      
          }
      
      }
      
    • 线程安全:JVM保证静态内部类加载时是线程安全的,且只有在调用getInstance时才加载SingletonHolder,实现懒加载。

    • 优缺点:兼顾懒加载和线程安全,推荐使用。

  5. 枚举单例(Enum Singleton)

    • 实现:利用Java枚举类型的特性。

    • 代码示例

      public enum Singleton {
          INSTANCE;
          public void doSomething() {
              // 业务逻辑
          }
      
      }
      
    • 线程安全:JVM保证枚举的实例化是线程安全的,且能防止反射和序列化破坏单例。

    • 优缺点:简洁、安全,但不适合复杂的初始化逻辑。

保证线程安全的关键点
  • 饿汉式和枚举:天生线程安全,依赖JVM类加载机制。

  • 懒汉式:需加锁(如synchronized)或使用双重检查锁。

  • 双重检查锁:结合volatile和synchronized,防止指令重排序和多线程竞争。

  • 静态内部类:利用JVM类加载机制,延迟加载且线程安全。

  • 序列化和反射攻击

    • 防止反射:构造函数抛出异常或使用枚举。

    • 防止序列化破坏:在类中添加

      readResolve

   private Object readResolve() {
        return instance;
    

2. 策略模式(Strategy Pattern)

定义

策略模式是一种行为型设计模式,定义一系列算法(策略),将每个算法封装起来,并使它们可以互换。客户端可以根据需要选择不同的策略,而不改变调用代码。

核心组成
  • 抽象策略接口(Strategy):定义算法的接口。
  • 具体策略类(ConcreteStrategy):实现具体算法。
  • 上下文类(Context):持有策略接口的引用,负责调用具体策略。
代码示例
// 策略接口

interface Strategy {
    int execute(int a, int b);
}

// 具体策略:加法
class AddStrategy implements Strategy {
    @Override
    public int execute(int a, int b) {
        return a + b;
    }
}



// 具体策略:减法

class SubtractStrategy implements Strategy {

    @Override
    public int execute(int a, int b) {
        return a - b;
    }
}



// 上下文

class Context {

    private Strategy strategy;
    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }
    public int executeStrategy(int a, int b) {
        return strategy.execute(a, b);
    }
}



// 使用

public class Main {

    public static void main(String[] args) {
        Context context = new Context();
        context.setStrategy(new AddStrategy());
        System.out.println(context.executeStrategy(5, 3)); // 输出 8
        context.setStrategy(new SubtractStrategy());
        System.out.println(context.executeStrategy(5, 3)); // 输出 2

    }

}
使用场景
  • 多种算法或行为:当一个类有多种行为,且这些行为可以根据上下文动态切换时(如支付方式:微信、支付宝、银行卡)。

  • 避免条件语句:替代大量if-else或switch语句,使代码更清晰。

  • 算法独立性:需要将算法与客户端代码解耦,方便扩展和维护。

  • 典型案例

    • 排序算法选择(如快速排序、归并排序)。
    • 支付系统(不同支付方式)。
    • 游戏中的角色技能(不同技能效果)。
优缺点
  • 优点:灵活、可扩展,符合开闭原则;代码复用性高。
  • 缺点:客户端需要了解所有策略;策略类可能较多。

3. 模板方法模式(Template Method Pattern)

定义

模板方法模式是一种行为型设计模式,定义一个操作的算法骨架,将某些步骤延迟到子类实现。父类控制算法流程,子类提供具体实现。

核心组成
  • 抽象模板类(AbstractClass):定义算法骨架(模板方法)和抽象方法。
  • 具体子类(ConcreteClass):实现抽象方法,提供具体逻辑。
代码示例
// 抽象模板类

abstract class AbstractClass {
    // 模板方法,定义算法骨架
    public final void templateMethod() {
        step1();
        step2();
        step3();
    }
    protected abstract void step1();
    protected abstract void step2();
    protected void step3() { // 可选的钩子方法 也就是子类可以选择性进行重写,不重写默认执行父类方法
        System.out.println("Default step3");
    }
}

// 具体子类

class ConcreteClass extends AbstractClass {

    @Override
    protected void step1() {
        System.out.println("ConcreteClass: Step 1");
    }
    @Override
    protected void step2() {
        System.out.println("ConcreteClass: Step 2");
    }
    @Override
    protected void step3() {
        System.out.println("ConcreteClass: Custom Step 3");
    }
}

// 使用

public class Main {
    public static void main(String[] args) {
        AbstractClass process = new ConcreteClass();
        process.templateMethod();

    }

}
使用场景
  • 固定算法骨架:当多个类共享相同的算法流程,但部分步骤的实现不同(如数据处理流程:读取、处理、保存)。

  • 代码复用:通过父类定义公共逻辑,子类只实现差异化部分。

  • 控制子类扩展:通过final模板方法限制子类修改算法结构。

  • 典型案例

    • 框架中的生命周期方法(如Spring的ApplicationContext初始化)。
    • 游戏开发中关卡流程(加载、运行、结束)。
    • 报表生成(数据采集、格式化、输出)。
优缺点
  • 优点:提高代码复用性;算法结构统一,易于维护;符合开闭原则。

  • 缺点:子类数量可能增多;父类设计复杂时可能限制灵活性。

  • 使用场景 适合抽象类 适合接口
    需要代码复用 ✅ 适合,支持方法和成员变量实现 ❌ 不适合(除非 default 方法)
    表示“是什么”(is-a) ✅ 抽象类适合建层次结构 ❌ 接口更适合“能做什么”
    表示“能做什么”(has ability to) ✅ 非常适合,比如 Serializable, Runnable
    要求多个类共享部分逻辑 ✅ 用抽象类抽取通用部分 ❌ 接口不适合写逻辑实现
    实现多个类型/能力组合 ❌ 不能多继承 ✅ 接口天生支持多继承

总结对比

  • 单例模式:确保单一实例,关注对象创建,需考虑线程安全(如双重检查锁、静态内部类)。
  • 策略模式:关注行为切换,适合动态选择算法,解耦客户端与算法实现。
  • 模板方法模式:关注算法骨架,适合固定流程但细节可变,强调继承和复用。

拓展:责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,用于将请求的发送者和接收者解耦,使多个对象都有机会处理这个请求。该模式将处理请求的对象组成一条链,请求沿着这条链传递,直到被某个对象处理为止。

结构图(类图)

Client --> Handler1 --> Handler2 --> Handler3 --> …
核心类图包括:

Handler(抽象处理者)

定义处理请求的接口。

持有下一个处理者的引用。

ConcreteHandler(具体处理者)

实现请求处理的逻辑。

如果自己不能处理,则将请求转发给下一个处理者。

Client(客户端)

创建处理链,并将请求传入第一个处理者。

** 应用场景
审批流程(如:员工请假、软件发布等)**

Java Web 的 Filter 过滤器链

Spring Security 的认证授权链

Netty 的 ChannelPipeline

🧑‍💻 Java 示例(审批流程)
比如一个请假流程,组长可以审批 1 天,经理可以审批 3 天,总监可以审批 7 天:

抽象处理者


```java
public abstract class LeaveHandler {
    protected LeaveHandler next;

    public void setNext(LeaveHandler next) {
        this.next = next;
    }

    public abstract void handleRequest(int days);
}

具体处理者

public class TeamLeader extends LeaveHandler {
    @Override
    public void handleRequest(int days) {
        if (days <= 1) {
            System.out.println("组长审批了 " + days + " 天的假期");
        } else if (next != null) {
            next.handleRequest(days);
        }
    }
}

public class Manager extends LeaveHandler {
    @Override
    public void handleRequest(int days) {
        if (days <= 3) {
            System.out.println("经理审批了 " + days + " 天的假期");
        } else if (next != null) {
            next.handleRequest(days);
        }
    }
}

public class Director extends LeaveHandler {
    @Override
    public void handleRequest(int days) {
        if (days <= 7) {
            System.out.println("总监审批了 " + days + " 天的假期");
        } else {
            System.out.println("假期太长,不批准");
        }
    }
}

客户端调用

public class Client {
    public static void main(String[] args) {
        LeaveHandler teamLeader = new TeamLeader();
        LeaveHandler manager = new Manager();
        LeaveHandler director = new Director();

        teamLeader.setNext(manager);
        manager.setNext(director);

        teamLeader.handleRequest(2); // 输出:经理审批了 2 天的假期
        teamLeader.handleRequest(6); // 输出:总监审批了 6 天的假期
        teamLeader.handleRequest(10); // 输出:假期太长,不批准
    }
}

1.责任链模式的优点?
解耦请求发送者和接收者。
动态调整处理链,灵活性高。
单一职责,每个处理者专注特定请求。
2.缺点?
请求可能未被处理。
链过长影响性能。
调试复杂。

3.使用场景?
日志系统(如不同级别日志处理)。
事件处理(如 GUI 事件传递)。
审批流程(如逐级审批)。

4.如何避免请求未被处理?
设置默认处理者。
确保链配置完整。


网站公告

今日签到

点亮在社区的每一天
去签到