单例模式是一种广泛使用的创建型设计模式,用于限制应用程序创建的对象。如果您在多线程环境中使用它,那么单例类的线程安全性非常重要。在现实世界的应用程序中,诸如数据库连接或企业信息系统(EIS)等资源是有限的,应该明智地使用,以避免资源短缺。为了实现这一点,我们可以实现一个单例设计模式。我们可以为资源创建一个包装器类,并在运行时限制创建的对象数量为一个。
Java中的线程安全单例
通常,我们遵循以下步骤创建单例类:
使用上述步骤,我创建了一个看起来像下面这样的单例类。ASingleton.java
package com.journaldev.designpatterns;
public class ASingleton {
private static ASingleton instance = null;
private ASingleton() {
}
public static ASingleton getInstance() {
if (instance == null) {
instance = new ASingleton();
}
return instance;
}
}
在上述代码中,getInstance()方法不是线程安全的。多个线程可以同时访问它。对于前几个线程,当实例变量未初始化时,多个线程可以进入if循环并创建多个实例。这将破坏我们的单例实现。
如何实现单例类的线程安全?
有三种方法可以实现线程安全。
- 在类加载时创建实例变量。
优点:
- 无需同步即可实现线程安全
- 易于实现
缺点:
- 提前创建可能不会在应用程序中使用的资源。
- 客户端应用程序无法传递任何参数,因此我们无法重用它。例如,为客户端应用程序提供数据库服务器属性的通用单例类中的数据库连接。
- 同步getInstance()方法。
优点:
- 线程安全性得到了保证。
- 客户端应用程序可以传递参数
- 延迟初始化已实现
缺点:
- 由于锁定开销而导致性能较慢。
- 不必要的同步在实例变量初始化后不再需要。
- 在if循环内使用同步块和volatile变量
优点:
- 线程安全性得到了保证
- 客户端应用程序可以传递参数
- 延迟初始化已实现
- 同步开销最小,仅适用于变量为null时的前几个线程。
缺点:
- 额外的if条件
观察了实现线程安全性的三种方式,我认为第三种是最佳选择。在这种情况下,修改后的类将如下所示:
package com.journaldev.designpatterns;
public class ASingleton {
private static volatile ASingleton instance;
private static Object mutex = new Object();
private ASingleton() {
}
public static ASingleton getInstance() {
ASingleton result = instance;
if (result == null) {
synchronized (mutex) {
result = instance;
if (result == null)
instance = result = new ASingleton();
}
}
return result;
}
}
本地变量result
似乎是不必要的。但是,它在那里是为了提高我们代码的性能。在实例已经初始化的情况下(大多数情况下),由于“return result;”而不是“return instance;”,volatile字段仅访问一次。这可以将方法的整体性能提高多达25%。如果您认为有更好的方法来实现这一点,或者如果上述实现中线程安全性受到损害,请发表评论并与我们分享。
奖金提示
String不是与`synchronized`关键字一起使用的很好的候选项。这是因为它们存储在字符串池中,我们不希望锁定可能被其他代码使用的字符串。因此,我正在使用一个对象变量。了解更多关于Java中线程安全和同步的知识。
您可以从我们的GitHub存储库中查看更多Java示例。
Source:
https://www.digitalocean.com/community/tutorials/thread-safety-in-java-singleton-classes