不使用锁如何实现线程安全的单例?

典型回答 如果不能使用synchronized和lock的话,想要实现单例可以通过饿汉模式、枚举、以及静态内部类的方式实现。 饿汉,其实都是通过定义静态的成员变量,以保证instance可以在类初始化的时候被实例化。 静态内部类,这种方式和饿汉方式只有细微差别,只是做法上稍微优雅一点。这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。。。但是,原理和饿汉一样。 枚举,其实,如果把枚举类进行反编译,你会发现他也是使用了static final来修饰每一个枚举项。 其实,上面三种方式,都是依赖静态数据在类初始化的过程中被实例化这一机制的。但是,如果真要较真的话,ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。也正是因为这样, 除非被重写,这个方法默认在整个装载过程中都是同步的(线程安全的)。 那么,除了上面这三种,还有一种无锁的实现方式,那就是CAS。 扩展知识 CAS实现线程安全的单例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Singleton { private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>(); private Singleton() {} public static Singleton getInstance() { for (;;) { Singleton singleton = INSTANCE.get(); if (null != singleton) { return singleton; } singleton = new Singleton(); if (INSTANCE.compareAndSet(null, singleton)) { return singleton; } } } } 用CAS的好处在于不需要使用传统的锁机制来保证线程安全。 ...

March 22, 2026 · 1 min · santu

为什么说枚举是实现单例最好的方式?

典型回答 《Effective Java》一书中,明确表达过一种观点: 使用枚举实现单例的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。 究其原因,主要有以下三个好处: 1、枚举实现的单例写法简单 2、枚举实现的单例天然是线程安全的 3、枚举实现的单例可避免被反序列化破坏 扩展知识 枚举单例写法简单 如果你看过《单例模式的多种写法》中的实现单例的所有方式的代码,那就会发现,各种方式实现单例的代码都比较复杂。主要原因是在考虑线程安全问题。 我们简单对比下“双重校验锁”方式和枚举方式实现单例的代码。 “双重校验锁”实现单例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } 枚举实现单例: ...

March 22, 2026 · 1 min · santu

什么是享元模式,有哪些具体应用?

典型回答 享元模式是一种通过尽可能多地共享数据来最小化内存使用和对象数量,从而提高性能的设计模式。在享元模式中,如果需要相同数据的多个对象,则共享这些对象而不是创建新的对象,从而提高系统的效率。 其实有很多应用场景,我们日常经常能接触到,但是很多人并不知道这其实是享元模式,如: 字符串池:在Java中,String对象使用了享元模式,通过字符串池的方式共享相同的字符串对象,避免了重复创建。 其实,很多池化技术,如数据库连接池、线程池等,背后都是采用了享元模式来共享对象的。 在服务器端开发中,享元模式也经常被使用,可以用来管理网络连接,避免资源的浪费。 扩展知识 示例 假设我们正在编写一个简单的游戏,这个游戏需要绘制很多小怪兽,每个小怪兽有不同的颜色、形状和属性。为了避免创建太多的怪兽对象占用过多的内存,我们可以使用享元模式来实现。 首先,我们创建一个抽象的怪兽类 Monster,包含所有怪兽共有的属性和方法,例如 draw() 方法来绘制怪兽。 1 2 3 public abstract class Monster { public abstract void draw(); } 然后,我们创建具体的怪兽类,例如红色怪兽、蓝色怪兽、方形怪兽、圆形怪兽等等。这些怪兽类继承自 Monster 类,并在构造方法中初始化它们特有的属性,例如颜色和形状 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class RedMonster extends Monster { private String color = "red"; @Override public void draw() { System.out.println("Draw a " + color + " monster"); } } public class BlueMonster extends Monster { private String color = "blue"; @Override public void draw() { System.out.println("Draw a " + color + " monster"); } } public class SquareMonster extends Monster { private String shape = "square"; @Override public void draw() { System.out.println("Draw a " + shape + " monster"); } } public class CircleMonster extends Monster { private String shape = "circle"; @Override public void draw() { System.out.println("Draw a " + shape + " monster"); } } 接下来,我们创建一个工厂类 MonsterFactory 来管理怪兽对象的创建和共享。工厂类维护一个 HashMap 对象,用来存储已经创建的怪兽对象。在获取怪兽对象时,如果该对象已经存在,直接返回已有的对象,否则创建一个新的对象并将其存储到 HashMap 中。这样,我们就可以确保每种属性的怪兽只创建一次,从而实现共享。 ...

March 22, 2026 · 2 min · santu

String的设计,用到了哪些设计模式?

典型回答 String的设计,用到了不可变模式和享元模式。 在Java中,String对象使用了享元模式,即在内存中共享相同的字符串常量。当创建一个新的字符串对象时,会先在字符串池中查找是否已经存在相同的字符串常量,如果存在,则直接返回该常量的引用;如果不存在,则创建一个新的字符串常量,并将其加入到字符串池中,以便以后的重复使用。 这种共享字符串常量的机制可以大大减少内存的使用,因为同一个字符串常量在内存中只会存在一份拷贝,而不同的字符串对象可以共享同一个字符串常量,避免重复创建相同的字符串对象。 ✅什么是享元模式,有哪些具体应用? String对象还使用了不可变模式,即一旦创建了一个字符串对象,就不能再修改其内容。这是通过将String类中的字符数组定义为private final的方式实现的,即该字符数组一旦被初始化,就不能再修改其内容,保证了字符串对象的不可变性。 这种不可变模式带来了一些好处,如线程安全、安全性、可靠性等。因为不可变的对象在多线程环境下是线程安全的,可以被多个线程共享,不需要进行额外的同步操作。同时,不可变的对象在安全性和可靠性方面也有优势,因为一旦对象创建完成,就不会再被修改,避免了意外修改导致的问题。 ✅什么是不可变模式,有哪些应用?

March 22, 2026 · 1 min · santu

什么是状态模式,有哪些应用?

典型回答 **状态模式允许一个对象在其内部状态发生改变时改变它的行为,使其看起来像是修改了其类。**它通过将对象的行为包装在不同状态对象中,实现了在运行时更改对象的状态,从而影响其行为。 状态模式也有很多实际的应用场景,如: 订单状态管理:订单状态有很多种,如未付款、已付款、已发货、已签收等。不同状态下,订单的行为也不同。 游戏角色状态:游戏角色的状态有很多种,如待机、行走、攻击、受伤等。不同状态下,角色的行为也不同。 音视频播放器:音视频播放器的状态有很多种,如播放、暂停、停止、快进、快退等。不同状态下,播放器的行为也不同。 在实际应用中,状态模式通常需要和其他设计模式结合使用,例如工厂模式、单例模式、策略模式等,以实现更灵活和高效的代码设计。 扩展知识 示例 假设我们正在为一个订单系统,需要管理他的状态,比如它包含了已创建、已支付、已发货、已完成、已取消等状态。 首先定义一个状态接口 1 2 3 4 5 public interface OrderState { void next(Order order); void previous(Order order); void printStatus(); } 然后定义具体的状态类,为每个具体状态实现上述接口。这里以两个状态为例:已创建和已支付。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class CreatedState implements OrderState { @Override public void next(Order order) { order.setState(new PaidState()); } @Override public void previous(Order order) { System.out.println("The order is in its root state."); } @Override public void printStatus() { System.out.println("Order created."); } } public class PaidState implements OrderState { @Override public void next(Order order) { order.setState(new ShippedState()); } @Override public void previous(Order order) { order.setState(new CreatedState()); } @Override public void printStatus() { System.out.println("Order paid."); } } 最后定义一个Order类,持有一个状态对象的引用,通过该状态对象的方法实现状态的切换。 ...

March 22, 2026 · 2 min · santu

什么是责任链模式,有哪些应用?

典型回答 责任链模式的目的是避免请求发送者与多个接收者之间的耦合关系,将这些接收者组成一条链,并沿着这条链传递请求,直到有一个接收者处理它为止。 在责任链模式中,通常将处理请求的对象称为处理器或者链的节点,每个节点都包含了处理该请求的逻辑以及指向下一个节点的引用。当请求到达一个节点时,如果该节点无法处理该请求,它会将请求转发给下一个节点,直到有一个节点处理该请求或者整个链都无法处理该请求。 责任链模式在实际开发中有很多应用场景,比如: 过滤器链:在Web开发中,可以通过责任链模式来实现过滤器链,例如Spring框架中的FilterChain就是一条责任链,每个过滤器都有机会对请求进行处理,直到最后一个过滤器处理完毕。 日志记录器:在日志系统中,可以使用责任链模式来将日志记录器组成一条链,从而实现多种日志记录方式的灵活组合。 异常处理器:在应用程序中,可以使用责任链模式来实现异常处理器的链式调用,从而灵活地处理各种异常情况。 授权认证:在系统中,可以使用责任链模式来实现授权认证的链式调用,从而灵活地控制不同用户对系统的访问权限。 扩展知识 示例 下面以一个订单处理的场景为例,介绍如何使用责任链模式: 假设我们有一个在线商店,当用户下单时,订单需要经过以下几个步骤: 检查订单信息是否完整 检查商品库存是否充足 检查用户余额是否充足 确认订单,更新商品库存和用户余额 我们可以将每个步骤封装成一个处理者,然后使用责任链模式将它们连接起来,形成一个处理链。 首先定义一个处理者接口 OrderHandler: 1 2 3 public interface OrderHandler { void handle(Order order); } 然后实现每个步骤对应的Handler: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class CheckOrderHandler implements OrderHandler { private OrderHandler next; public CheckOrderHandler(OrderHandler next) { this.next = next; } @Override public void handle(Order order) { // 检查订单信息是否完整 if (order.isInfoComplete()) { // 如果订单信息完整,则将请求传递给下一个处理者 next.handle(order); } else { // 如果订单信息不完整,则直接返回错误信息 throw new RuntimeException("订单信息不完整"); } } } public class CheckStockHandler implements OrderHandler { private OrderHandler next; public CheckStockHandler(OrderHandler next) { this.next = next; } @Override public void handle(Order order) { // 检查商品库存是否充足 if (order.getStock() >= order.getQuantity()) { // 如果库存充足,则将请求传递给下一个处理者 next.handle(order); } else { // 如果库存不足,则直接返回错误信息 throw new RuntimeException("商品库存不足"); } } } public class CheckBalanceHandler implements OrderHandler { private OrderHandler next; public CheckBalanceHandler(OrderHandler next) { this.next = next; } @Override public void handle(Order order) { // 检查用户余额是否充足 if (order.getBalance() >= order.getAmount()) { // 如果余额充足,则将请求传递给下一个处理者 next.handle(order); } else { // 如果余额不足,则直接返回错误信息 throw new RuntimeException("用户余额不足"); } } } public class ConfirmOrderHandler implements OrderHandler { @Override public void handle(Order order) { // 确认订单,更新商品库存和用户余额 order.confirm(); } } 其中每个处理者都有一个指向下一个处理者的引用,处理者之间通过调用下一个处理者的 handle 方法将请求传递下去。如果某个处理者无法处理请求,则直接返回错误信息。 ...

March 22, 2026 · 2 min · santu

什么是代理模式,有哪些应用?

典型回答 代理模式是一种结构设计模式,它允许通过创建代理对象来控制对其他对象的访问。代理对象充当原始对象的接口,客户端通过代理对象间接地访问原始对象,并可以在访问过程中添加额外的逻辑或控制。 代理模式的主要目的是通过引入代理对象,为原始对象提供一层间接访问的方式,以实现对原始对象的控制、保护或增强。他的常用场景有以下几个: 1、远程代理:在分布式系统中,代理模式可用于代理远程对象。远程代理隐藏了远程对象的实际实现细节,使客户端可以像访问本地对象一样访问远程对象。如Dubbo的实现就是用到了代理模式。 ✅Dubbo如何实现像本地方法一样调用远程方法的? 2、动态代理:动态代理允许在运行时动态地创建代理对象,并动态地将方法调用分派到不同的处理器。它通过Java的反射机制实现,可以用于实现通用的代理逻辑,而无需为每个被代理的类单独创建代理。如Spring的AOP,就用到了动态代理。 ✅介绍一下Spring的AOP 3、缓存代理:缓存代理可以缓存原始对象的结果,以避免重复计算或访问资源。一般我们在用到缓存的时候,可以用这种模式。先访问代理对象,代理对象会去查询缓存,如果缓存中你没有,再去查询真实对象。 4、日志代理:这种用的也挺多的,当我需要做日志记录的时候,可以做一个代理,在代理对象中进行统一的日志记录及管理。 5、异常代理:通常我们的系统中如果有统一的异常机制或者ERROR_CODE的机制,可以通过创建一个统一的代理来做处理。在代理对象中这些异常的捕捉及转换。 扩展知识 示例 假设我们有一个比较耗时的查询服务,他的接口定义及实现如下: 1 2 3 4 5 6 /** * @Author Hollis **/ public interface DataService { String getData(); } 1 2 3 4 5 6 7 8 9 10 /** * @Author Hollis **/ public class DataServiceImpl implements DataService { @Override public String getData() { //执行非常耗时的数据查询 return "Data from expensive operation"; } } 这时候我们想要引入缓存,则可以定义以一个代理: ...

March 22, 2026 · 1 min · santu

什么是模板方法模式,有哪些应用?

典型回答 模板方法模式是一种行为设计模式,他的主要作用就是复用代码。在很多时候,我们的代码中可能会有一些公共的部分并且还有一些定制的部分,那么公共这部分就可以定义在一个父类中,然后将定制的部分实现在子类中。这样子类可以根据需要扩展或重写父类的方法,而不需要改变算法的结构。 我们通常会把模板方法模式和策略模式一起使用,因为当我们使用策略模式的时候,会把具体的策略实现在策略服务里面,但是还剩下一些通用的逻辑,就可以通过模板方法模式进行复用。 ✅你在工作中是如何使用设计模式的? 扩展知识 示例 我们拿一个常见的优惠券作为示例,假设我们需要定义一个优惠券的申请服务。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 abstract class Coupon { // 模板方法,定义优惠券的应用流程 public final void applyCoupon() { if (isCouponValid()) { if (isEligibleForDiscount()) { applyDiscount(); } displayConfirmation(); } else { displayInvalidCouponMessage(); } } // 具体方法,用于判断优惠券是否有效 protected boolean isCouponValid() { // 具体的判断逻辑,子类可以重写该方法来实现特定的有效性判断 return true; } // 具体方法,用于判断用户是否符合优惠券的折扣条件 protected boolean isEligibleForDiscount() { // 具体的判断逻辑,子类可以重写该方法来实现特定的条件判断 return true; } // 抽象方法,由子类实现具体的优惠券折扣逻辑 protected abstract void applyDiscount(); // 抽象方法,由子类实现具体的优惠券确认展示逻辑 protected abstract void displayConfirmation(); // 具体方法,用于展示无效优惠券的信息 protected void displayInvalidCouponMessage() { System.out.println("无效优惠券!"); } } 以上是一个抽象类,这个类中有一个具体的方法applyCoupon,其中定义了一个优惠券申请的具体实现,并且编排了多个其他的方法。 ...

March 22, 2026 · 2 min · santu

什么是观察者模式,有哪些应用?

典型回答 观察者模式是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。这种模式有助于实现松耦合,因为被观察者对象无需知道观察者的具体细节,只需通知它们即可。 观察者模式在许多应用场景中都能够发挥作用,特别是在需要处理对象之间的松耦合关系、实时通知和更新的情况下。以下是观察者模式常见的应用场景: 发布-订阅系统:观察者模式是发布-订阅模式的核心。当发布者发布新消息或事件时,所有订阅者都会收到通知并执行相应的操作。 事件处理机制:观察者模式用于处理事件驱动的编程。事件触发时,事件源对象会通知事件处理程序(观察者),以执行相应的操作。 实时数据更新:在需要实时更新数据的应用中,观察者模式可以用于将数据源与数据消费者连接起来。当数据源的数据发生变化时,观察者可以自动获取最新的数据并进行处理。 库和框架:许多编程库和框架使用观察者模式来支持插件和扩展。开发人员可以编写自定义观察者以响应库或框架中的事件或回调。 消息队列系统:观察者模式可用于消息队列系统,其中生产者将消息发送到队列,而消费者作为观察者订阅队列以接收和处理消息。 股票市场监测:股票市场应用程序可以使用观察者模式来监测股票价格变化,并将这些变化通知给投资者。 游戏开发:在游戏中,观察者模式可用于处理各种事件,如玩家输入、碰撞检测、角色状态变化等。 网络通信:在网络应用中,观察者模式可用于实现即时通信系统,其中用户之间的消息传递可以通过观察者模式来实现。 扩展知识 示例 我们先创建一个主题,然后期望他在被修改的时候可以被立刻感知到。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import java.util.ArrayList; import java.util.List; public class Subject { private List<Observer> observers = new ArrayList<>(); private int state; public int getState() { return state; } public void setState(int state) { this.state = state; notifyAllObservers(); } public void attach(Observer observer) { observers.add(observer); } public void notifyAllObservers() { for (Observer observer : observers) { observer.update(); } } } 这里面定义了一个成员变量List<Observer> observers ,这就是这个主题的观察者的列表。Observer 的定义如下: ...

March 22, 2026 · 1 min · santu

策略模式和if-else相比有什么好处?

典型回答 策略模式是一种行为设计模式,它允许在运行时根据不同情况选择算法的不同实现。它将算法和其相应的行为封装在一个独立的类中,使得它们可以相互替换,而不会影响客户端的使用。这种模式支持开闭原则,即在不修改现有客户端代码的情况下,可以动态地添加、删除或替换算法。 策略模式相较于if-else语句,有以下几个优势: 易于扩展:使用策略模式,可以方便地增加、删除或更换算法,而不需要修改原有的代码,只需要添加新的策略类即可。 更好的可读性:策略模式可以将复杂的条件语句分散到不同的策略类中,使得代码更加清晰、易于理解和维护。 避免大量的条件判断:在if-else语句中,可能需要写很多的条件判断,当条件越来越多时,代码变得复杂、难以维护。而使用策略模式,可以将条件判断分散到不同的策略类中,每个策略类只需要关注自己的逻辑,使得代码更加简洁。 提高代码复用性:策略模式可以将一些常用的算法封装在策略类中,可以被多个客户端共享使用,从而提高代码的复用性。 一般在实际应用中,策略模式会结合工厂模式、模板方法模式一起使用。 扩展知识 示例 我们结合策略+工厂+模板方法模式,看一下如何在Spring中使用。 假设有一个订单处理系统,处理订单的流程包括如下步骤: 根据订单类型选择不同的处理策略(如普通订单、团购订单、秒杀订单等); 每个订单类型的处理策略可能有所不同,但是都需要经过一些公共的处理流程,比如记录日志、验证订单信息等; 处理完订单后,需要将处理结果返回给调用方。 首先,定义订单处理策略的接口: 1 2 3 public interface OrderProcessStrategy { void process(Order order); } 然后定义一个公共的基础实现类,其中包含了订单的前置处理和后置处理: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public abstract class BaseOrderProcessStrategy implements OrderProcessStrategy { public void process(Order order){ //前置处理 checkOrder(order); doProcess(order); //后置处理 //doLog(order); } public abstract void doProcess(Order order); public void checkOrder(Order order){ //订单检查业务逻辑 } public void doLog(Order order){ //记录日志相关代码 } } 接下来,定义不同类型订单的处理策略实现类,集成BaseOrderProcessStrategy这个抽象类,并且实现其中的doProcess方法。 ...

March 22, 2026 · 2 min · santu

留言给博主