北京JAVA基础面试30天打卡04
1. 单例模式的实现方式及线程安全
单例模式(Singleton Pattern)确保一个类只有一个实例,并提供一个全局访问点。以下是常见的单例模式实现方式,以及如何保证线程安全:
单例模式的实现方式
-
饿汉式(Eager Initialization)
-
实现:在类加载时就创建实例(静态初始化)。
-
代码示例
public class Singleton {private static final Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}}
-
线程安全:天生线程安全,因为实例在类加载时创建,JVM保证类加载过程是线程安全的。
-
优缺点:简单,但可能会导致资源浪费(如果实例未被使用)。
-
-
懒汉式(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关键字保证线程安全,但锁粒度较大,性能较低。
-
-
双重检查锁(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防止指令重排序,确保实例初始化完成前其他线程不会访问;双重检查减少锁的开销。
-
优缺点:性能较高,但代码稍复杂。
-
-
静态内部类(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,实现懒加载。
-
优缺点:兼顾懒加载和线程安全,推荐使用。
-
-
枚举单例(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 {@Overridepublic int execute(int a, int b) {return a + b;}
}// 具体策略:减法class SubtractStrategy implements Strategy {@Overridepublic 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)); // 输出 8context.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 {@Overrideprotected void step1() {System.out.println("ConcreteClass: Step 1");}@Overrideprotected void step2() {System.out.println("ConcreteClass: Step 2");}@Overrideprotected 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 {@Overridepublic void handleRequest(int days) {if (days <= 1) {System.out.println("组长审批了 " + days + " 天的假期");} else if (next != null) {next.handleRequest(days);}}
}public class Manager extends LeaveHandler {@Overridepublic void handleRequest(int days) {if (days <= 3) {System.out.println("经理审批了 " + days + " 天的假期");} else if (next != null) {next.handleRequest(days);}}
}public class Director extends LeaveHandler {@Overridepublic 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.如何避免请求未被处理?
设置默认处理者。
确保链配置完整。