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

2020-05-21 16:02:32来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

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

作者:Yujiaao
https://segmentfault.com/a/1190000019962661

一个很艰难的?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 的最佳方式。你仍然可以使用其他流行的方法, 如你觉得更好, 欢迎讨论。

推荐去我的博客阅读更多:

1.Java JVM、集合、多线程、新特性系列教程

2.Spring MVC、Spring Boot、Spring Cloud 系列教程

3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

4.Java、后端、架构、阿里巴巴等大厂最新面试题

觉得不错,别忘了点赞+转发哦!


原文链接:https://www.cnblogs.com/javastack/p/12930609.html
如有疑问请与原作者联系

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:教你在 IntelliJ IDEA 中使用 VIM!

下一篇:Java窗体加载时不显示组件或需要重置窗口