单例模式是一种确保某个类在程序中只有一个实例,并提供全局访问点的设计模式。以下是几种常见的单例模式实现方式及其线程安全保证方法:
饿汉式
饿汉式单例在类加载时就创建好实例对象,因此在程序调用时直接返回该单例对象即可。由于类加载的过程是线程安全的,所以饿汉式单例不存在线程安全问题。
public class EagerSingleton {private static final EagerSingleton instance = new EagerSingleton();private EagerSingleton() {}public static EagerSingleton getInstance() {return instance;}
}
懒汉式
懒汉式单例在第一次被调用时才创建对象,但简单的懒汉式实现存在线程安全问题。可以通过在getInstance()
方法上添加synchronized
关键字来保证线程安全。
public class LazySingleton {private static LazySingleton instance;private LazySingleton() {}public static synchronized LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton();}return instance;}
}
双重检查锁定(DCL)
双重检查锁定是一种优化后的懒汉式实现,通过两次if
判断和synchronized
块来保证线程安全,同时避免了synchronized
方法的性能问题。
public class DCLSingleton {private volatile static DCLSingleton instance;private DCLSingleton() {}public static DCLSingleton getInstance() {if (instance == null) {synchronized (DCLSingleton.class) {if (instance == null) {instance = new DCLSingleton();}}}return instance;}
}
静态内部类
静态内部类单例利用了JVM的类加载机制,只有在第一次调用getInstance()
方法时才会加载内部类并创建单例实例,因此是线程安全的。
public class Singleton {private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}
枚举
枚举实现的单例模式是线程安全的,并且简单、高效,还能防止序列化和反射攻击。
public enum Singleton {INSTANCE;
}
线程安全保证方法
-
使用
synchronized
关键字:通过同步整个方法或代码块来保证线程安全,但会降低性能。 -
双重检查锁定(DCL):通过两次
if
判断和synchronized
块来保证线程安全,同时避免了性能问题。 -
利用JVM的类加载机制:静态内部类和枚举利用了JVM的类加载机制来保证线程安全,无需显式使用锁。
-
volatile
关键字:在DCL中,volatile
保证了线程之间的可见性,防止指令重排序带来的问题。 -
什么是策略模式?一般用在什么场景?
策略模式是一种行为型设计模式,它允许你定义一系列算法,把它们封装起来,并使它们可以互相替换。策略模式让你能够间接地选择算法,而无需修改使用该算法的代码。
核心思想
策略模式的核心思想是将算法或行为抽取出来,封装在独立的类中,这些类被称为策略类。然后,通过一个上下文类来使用这些策略类,从而实现算法或行为的动态切换。
主要组成部分
- 策略接口(Strategy):定义了所有支持的算法具有的公共接口。
- 具体策略类(Concrete Strategy):实现了策略接口,提供了具体的算法实现。
- 上下文类(Context):维护一个对策略对象的引用,并使用该策略对象来执行算法。
使用场景
策略模式适用于以下场景:
- 多种算法或行为需要互换使用:当一个系统需要根据不同的条件或用户选择来动态地切换算法或行为时,策略模式可以方便地实现这种切换。
- 封装算法或行为:当需要将算法或行为的实现细节封装起来,使客户端不需要知道其内部工作原理时,策略模式可以提供一个统一的接口来访问这些算法或行为。
- 消除条件语句:当代码中存在大量的条件语句来选择不同的算法或行为时,策略模式可以将这些条件语句替换为策略模式,使代码更加灵活和易于维护。
- 策略共享:当不同的对象需要共享相同的算法或行为时,策略模式可以通过共享策略对象来节省内存空间。
示例代码
以下是一个简单的策略模式示例,展示了如何实现不同的支付方式:
// 策略接口
public interface PaymentStrategy {void pay(double amount);
}// 具体策略类:信用卡支付
public class CreditCardPayment implements PaymentStrategy {private String cardNumber;private String name;public CreditCardPayment(String cardNumber, String name) {this.cardNumber = cardNumber;this.name = name;}@Overridepublic void pay(double amount) {System.out.println(amount + " paid with credit card.");}
}// 具体策略类:支付宝支付
public class AlipayPayment implements PaymentStrategy {private String email;public AlipayPayment(String email) {this.email = email;}@Overridepublic void pay(double amount) {System.out.println(amount + " paid with Alipay.");}
}// 上下文类
public class PaymentContext {private PaymentStrategy paymentStrategy;public PaymentContext(PaymentStrategy paymentStrategy) {this.paymentStrategy = paymentStrategy;}public void setPaymentStrategy(PaymentStrategy paymentStrategy) {this.paymentStrategy = paymentStrategy;}public void pay(double amount) {paymentStrategy.pay(amount);}
}// 客户端代码
public class Client {public static void main(String[] args) {PaymentContext context = new PaymentContext(new CreditCardPayment("1234567890", "John Doe"));context.pay(100);context.setPaymentStrategy(new AlipayPayment("john.doe@example.com"));context.pay(200);}
}
在这个示例中,PaymentStrategy
是策略接口,CreditCardPayment
和 AlipayPayment
是具体策略类,PaymentContext
是上下文类。客户端代码可以通过设置不同的支付策略来动态地切换支付方式。
模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法模式使得子类可以在不改变算法结构的前提下,重新定义算法中的某些步骤。
核心思想
模板方法模式的核心思想是将不变的行为封装在超类中,而将可变的行为抽象成抽象方法或钩子方法,让子类去实现这些方法。这样,不同的子类可以以不同的方式实现这些可变步骤,而算法的整体结构和流程由超类控制。
主要组成部分
- 抽象类(Abstract Class):定义了模板方法和一些基本操作。模板方法通常是一个具体方法,它定义了算法的骨架,调用基本操作和其他方法。
- 基本操作(Primitive Operations):这些是抽象方法或钩子方法,由子类实现。它们是算法中的可变部分。
- 模板方法(Template Method):这是一个具体方法,它定义了算法的骨架,调用基本操作和其他方法。子类通常不需要覆盖这个方法。
- 具体子类(Concrete Subclasses):实现了抽象类中的抽象方法,以提供具体的实现。
使用场景
模板方法模式适用于以下场景:
- 算法的某些步骤需要子类根据实际情况实现或重写:当一个算法的某些步骤在不同的子类中有不同的实现时,可以将这些步骤定义为抽象方法,让子类去实现。
- 控制子类的扩展:当希望限制子类对算法的修改,只允许在特定的地方进行扩展时,可以将算法的骨架定义在模板方法中,而将可变部分定义为抽象方法或钩子方法。
- 代码复用:当多个子类共享相同的算法结构,但某些步骤的实现不同时,可以将相同的部分提取到超类中,减少代码重复。
- 在算法中插入钩子方法:当需要在算法的某些固定点上插入额外的操作时,可以定义钩子方法,子类可以根据需要实现这些钩子方法。
示例代码
以下是一个简单的模板方法模式示例,展示了如何制作饮品的过程:
// 抽象类
public abstract class Beverage {// 模板方法,定义了算法的骨架public final void prepareRecipe() {boilWater();brew();pourInCup();addCondiments();}// 基本操作private void boilWater() {System.out.println("Boiling water");}private void pourInCup() {System.out.println("Pouring into cup");}// 抽象方法,由子类实现protected abstract void brew();protected abstract void addCondiments();
}// 具体子类:咖啡
public class Coffee extends Beverage {@Overrideprotected void brew() {System.out.println("Dripping Coffee through filter");}@Overrideprotected void addCondiments() {System.out.println("Adding Sugar and Milk");}
}// 具体子类:茶
public class Tea extends Beverage {@Overrideprotected void brew() {System.out.println("Steeping the tea");}@Overrideprotected void addCondiments() {System.out.println("Adding Lemon");}
}// 客户端代码
public class Client {public static void main(String[] args) {Beverage coffee = new Coffee();coffee.prepareRecipe();System.out.println();Beverage tea = new Tea();tea.prepareRecipe();}
}
在这个示例中,Beverage
是抽象类,定义了模板方法 prepareRecipe()
和一些基本操作。Coffee
和 Tea
是具体子类,实现了抽象方法 brew()
和 addCondiments()
。客户端代码通过创建不同的子类实例来执行不同的饮品制作过程。