一个很艰难的 Java 核心面试问题!

简介: 一个很艰难的 Java 核心面试问题,这个 Java 问题也常被问: 什么是线程安全的单例,你怎么创建它。

一个很艰难的 Java 核心面试问题,这个 Java 问题也常被问: 什么是线程安全的单例,你怎么创建它。


好吧,在Java 5之前的版本, 使用双重检查锁定创建单例 Singleton 时,如果多个线程试图同时创建Singleton实例,则可能有多个Singleton实例被创建。


从 Java 5 开始,使用 Enum 创建线程安全的Singleton`很容易。但如果面试官坚持双重检查锁定,那么你必须为他们编写代码。记得使用volatile变量。


为什么枚举单例在 Java 中更好

枚举单例是使用一个实例在 Java 中实现单例模式的新方法。虽然Java中的单例模式存在很长时间,但枚举单例是相对较新的概念,在引入Enum作为关键字和功能之后,从Java5开始在实践中。


本文与之前关于 Singleton 的内容有些相关, 其中讨论了有关 Singleton 模式的面试中的常见问题, 以及 10 个 Java 枚举示例, 其中我们看到了如何通用枚举可以。


这篇文章是关于为什么我们应该使用Eeame作为Java中的单例,它比传统的单例方法相比有什么好处等等。


Java 枚举和单例模式

Java 中的枚举单例模式是使用枚举在 Java 中实现单例模式。单例模式在 Java 中早有应用, 但使用枚举类型创建单例模式时间却不长. 如果感兴趣, 你可以了解下构建者设计模式和装饰器设计模式。


1)枚举单例易于书写


这是迄今为止最大的优势,如果你在Java 5之前一直在编写单例, 你知道, 即使双检查锁定, 你仍可以有多个实例。


虽然这个问题通过 Java 内存模型的改进已经解决了, 从 Java 5 开始的 volatile 类型变量提供了保证, 但是对于许多初学者来说, 编写起来仍然很棘手。


与同步双检查锁定相比,枚举单例实在是太简单了。如果你不相信, 那就比较一下下面的传统双检查锁定单例和枚举单例的代码:


在 Java 中使用枚举的单例

这是我们通常声明枚举的单例的方式,它可能包含实例变量和实例方法,但为了简单起见,我没有使用任何实例方法,只是要注意,如果你使用的实例方法且该方法能改变对象的状态的话, 则需要确保该方法的线程安全。


默认情况下,创建枚举实例是线程安全的,但 Enum 上的任何其他方法是否线程安全都是程序员的责任。

/**
* 使用 Java 枚举的单例模式示例
*/
public enum EasySingleton{
    INSTANCE;
}

你可以通过EasySingleton.INSTANCE来处理它,这比在单例上调用getInstance()方法容易得多。


具有双检查锁定的单例示例

下面的代码是单例模式中双重检查锁定的示例,此处的 getInstance() 方法检查两次,以查看 INSTANCE 是否为空,这就是为什么它被称为双检查锁定模式,请记住,双检查锁定是代理之前Java 5,但Java 5内存模型中易失变量的干扰,它应该工作完美。

/**
* 单例模式示例,双重锁定检查
*/
public class DoubleCheckedLockingSingleton{
     private volatile DoubleCheckedLockingSingleton INSTANCE;
     private DoubleCheckedLockingSingleton(){}
     public DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

你可以调用DoubleCheckedLockingSingleton.getInstance() 来获取此单例类的访问权限。


现在,只需查看创建延迟加载的线程安全的 Singleton 所需的代码量。使用枚举单例模式, 你可以在一行中具有该模式, 因为创建枚举实例是线程安全的, 并且由 JVM 进行。


人们可能会争辩说,有更好的方法来编写 Singleton 而不是双检查锁定方法, 但每种方法都有自己的优点和缺点, 就像我最喜欢在类加载时创建的静态字段 Singleton, 如下面所示, 但请记住, 这不是一个延迟加载单例:


单例模式用静态工厂方法

这是我最喜欢的在 Java 中影响 Singleton 模式的方法之一,因为 Singleton 实例是静态的,并且最后一个变量在类首次加载到内存时初始化,因此实例的创建本质上是线程安全的。

/**
* 单例模式示例与静态工厂方法
*/
public class Singleton{
    //initailzed during class loading
    private static final Singleton INSTANCE = new Singleton();
    //to prevent creating another instance of Singleton
    private Singleton(){}
    public static Singleton getSingleton(){
        return INSTANCE;
    }
}

你可以调用 Singleton.getSingleton() 来获取此类的访问权限。


*2) 枚举单例自行处理序列化 *传统单例的另一个问题是,一旦实现可序列化接口,它们就不再是 Singleton, 因为 readObject() 方法总是返回一个新实例, 就像 Java 中的构造函数一样。通过使用 readResolve() 方法, 通过在以下示例中替换 Singeton 来避免这种情况:

//readResolve to prevent another instance of Singleton
private Object readResolve(){
    return INSTANCE;
}

如果 Singleton 类保持内部状态, 这将变得更加复杂, 因为你需要标记为 transient(不被序列化),但使用枚举单例, 序列化由 JVM 进行。


3) 创建枚举实例是线程安全的


如第 1 点所述,因为 Enum 实例的创建在默认情况下是线程安全的, 你无需担心是否要做双重检查锁定。关注微信公众号_Java技术栈_在后台回复_Java_可以获取我整理的Java 多线程干货。


总之, 在保证序列化和线程安全的情况下,使用两行代码枚举单例模式是在 Java 5 以后的世界中创建 Singleton 的最佳方式。你仍然可以使用其他流行的方法, 如你觉得更好, 欢迎讨论。


相关文章
|
3月前
|
Java 测试技术 微服务
最新技术栈下 Java 面试高频技术点实操指南详解
本指南结合最新Java技术趋势,涵盖微服务(Spring Cloud Alibaba)、响应式编程(Spring WebFlux)、容器化部署(Docker+Kubernetes)、函数式编程、性能优化及测试等核心领域。通过具体实现步骤与示例代码,深入讲解服务注册发现、配置中心、熔断限流、响应式数据库访问、JVM调优等内容。适合备战Java面试,提升实操能力,助力技术进阶。资源链接:[https://pan.quark.cn/s/14fcf913bae6](https://pan.quark.cn/s/14fcf913bae6)
164 25
|
3月前
|
缓存 Java 关系型数据库
2025 年最新华为 Java 面试题及答案,全方位打造面试宝典
Java面试高频考点与实践指南(150字摘要) 本文系统梳理了Java面试核心考点,包括Java基础(数据类型、面向对象特性、常用类使用)、并发编程(线程机制、锁原理、并发容器)、JVM(内存模型、GC算法、类加载机制)、Spring框架(IoC/AOP、Bean生命周期、事务管理)、数据库(MySQL引擎、事务隔离、索引优化)及分布式(CAP理论、ID生成、Redis缓存)。同时提供华为级实战代码,涵盖Spring Cloud Alibaba微服务、Sentinel限流、Seata分布式事务,以及完整的D
191 1
|
2月前
|
缓存 Java API
Java 面试实操指南与最新技术结合的实战攻略
本指南涵盖Java 17+新特性、Spring Boot 3微服务、响应式编程、容器化部署与数据缓存实操,结合代码案例解析高频面试技术点,助你掌握最新Java技术栈,提升实战能力,轻松应对Java中高级岗位面试。
328 0
|
3月前
|
存储 安全 Java
常见 JAVA 集合面试题整理 自用版持续更新
这是一份详尽的Java集合面试题总结,涵盖ArrayList与LinkedList、HashMap与HashTable、HashSet与TreeSet的区别,以及ConcurrentHashMap的实现原理。内容从底层数据结构、性能特点到应用场景逐一剖析,并提供代码示例便于理解。此外,还介绍了如何遍历HashMap和HashTable。无论是初学者还是进阶开发者,都能从中受益。代码资源可从[链接](https://pan.quark.cn/s/14fcf913bae6)获取。
202 3
|
3月前
|
存储 安全 Java
2025 最新史上最全 Java 面试题独家整理带详细答案及解析
本文从Java基础、面向对象、多线程与并发等方面详细解析常见面试题及答案,并结合实际应用帮助理解。内容涵盖基本数据类型、自动装箱拆箱、String类区别,面向对象三大特性(封装、继承、多态),线程创建与安全问题解决方法,以及集合框架如ArrayList与LinkedList的对比和HashMap工作原理。适合准备面试或深入学习Java的开发者参考。附代码获取链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
1268 48
|
3月前
|
算法 架构师 Java
Java 开发岗及 java 架构师百度校招历年经典面试题汇总
以下是百度校招Java岗位面试题精选摘要(150字): Java开发岗重点关注集合类、并发和系统设计。HashMap线程安全可通过Collections.synchronizedMap()或ConcurrentHashMap实现,后者采用分段锁提升并发性能。负载均衡算法包括轮询、加权轮询和最少连接数,一致性哈希可均匀分布请求。Redis持久化有RDB(快照恢复快)和AOF(日志更安全)两种方式。架构师岗涉及JMM内存模型、happens-before原则和无锁数据结构(基于CAS)。
104 5
|
3月前
|
Java API 微服务
2025 年 Java 校招面试全攻略:从面试心得看 Java 岗位求职技巧
《2025年Java校招最新技术要点与实操指南》 本文梳理了2025年Java校招的核心技术栈,并提供了可直接运行的代码实例。重点技术包括: Java 17+新特性(Record类、Sealed类等) Spring Boot 3+WebFlux响应式编程 微服务架构与Spring Cloud组件 Docker容器化部署 Redis缓存集成 OpenAI API调用 通过实际代码演示了如何应用这些技术,如Java 17的Record类简化POJO、WebFlux构建响应式API、Docker容器化部署。
135 5
|
3月前
|
缓存 NoSQL Java
Java Redis 面试题集锦 常见高频面试题目及解析
本文总结了Redis在Java中的核心面试题,包括数据类型操作、单线程高性能原理、键过期策略及分布式锁实现等关键内容。通过Jedis代码示例展示了String、List等数据类型的操作方法,讲解了惰性删除和定期删除相结合的过期策略,并提供了Spring Boot配置Redis过期时间的方案。文章还探讨了缓存穿透、雪崩等问题解决方案,以及基于Redis的分布式锁实现,帮助开发者全面掌握Redis在Java应用中的实践要点。
190 6
|
3月前
|
安全 Java API
2025 年 Java 校招面试常见问题及详细答案汇总
本资料涵盖Java校招常见面试题,包括Java基础、并发编程、JVM、Spring框架、分布式与微服务等核心知识点,并提供详细解析与实操代码,助力2025校招备战。
182 1
|
3月前
|
算法 Java 微服务
2025 年 Java 面试宝典社招春招秋招实操全方位攻略
2025年Java面试宝典涵盖核心技术及最新趋势,分为四大板块:1. Java基础:深入数据类型、多态等特性,结合学生信息管理等实例;2. JVM核心:解析内存模型与GC算法,附多线程转账等场景应用;3. 高并发方案:详解synchronized与线程池配置,提供Web服务器优化案例;4. Spring生态:剖析IoC/AOP原理,演示微服务架构实现。特别新增Java 17+特性实操,包括Record类、密封接口等语法糖,整合Spring Boot 3、响应式编程及云原生技术,通过订单状态机、API网关配置。
241 1