Java双重检查加锁单例模式的详解
单例模式是一种常见的设计模式,它保证一个类在运行时只有一个实例存在,并提供一种全局访问该实例的方法。Java双重检查加锁单例模式是单例模式的一种常见实现方式。
为什么需要双重检查加锁
单例模式通常通过私有构造函数和静态方法来实现。但是,在多线程环境下,多个线程同时访问单例类就可能导致多个实例的创建,这违背了单例模式的初衷。为了解决这个问题,我们需要考虑多线程环境下的实现方式。
当然,我们可以将该方法声明为 synchronized,该方法在多线程环境下具有互斥性,以确保只有一个实例被创建。但是,这种方式会有明显的性能问题。每次方法调用都会进行加锁和解锁操作,会降低程序的性能。
为了解决这个问题,我们可以使用双重检查加锁的方式。
双重检查加锁单例模式的实现
下面是一个简单的双重检查加锁的单例模式的实现。我们假设该类为 Singleton。
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;
}
}
这里使用了 volatile 和 synchronized 两个关键字来实现双重检查加锁。
volatile 关键字可以确保创建实例的同时,该实例对于所有线程都是可见的。也就是说,该实例的创建线程会通知其他线程该实例已经创建,防止其他线程重复创建实例。
synchronized 关键字用于互斥保护 singleton 的创建过程。互斥保护可以保证在同一时刻只有一个线程可以创建 Singleton 实例,从而避免了多个实例同时创建的问题。由于 synchronized 关键字只会影响创建过程,因此获取单例的操作不需要加锁,不会影响程序的性能。
另外,双重检查锁需要注意的是,singleton 变量必须使用 volatile 关键字进行声明。这是因为在 JVM 内部重排序的原因,如果不给 singleton 变量添加 volatile 关键字,将可能引起 “半初始化对象” 的情况,从而返回一个不完全初始化的实例。
示例说明
下面我们来看两个简单的示例来说明 Java双重检查加锁单例模式的使用。
示例一:缓存
缓存是程序中常用的一种技术,它可以提高程序的响应速度。通常情况下,缓存系统是唯一的,因此,单例模式是缓存系统实现的一个很好的选择。
我们可以使用一个包含缓存数据的 Singleton 类来实现一个包含 get 和 set 方法的缓存系统。缓存方法会首先从缓存中获取数据。如果缓存中不存在,则从数据库中获取,并将其存储在缓存中供后续使用。
public class Cache {
private static Cache instance;
private Map<String, Object> data;
private Cache() {
this.data = new HashMap<String, Object>();
}
public static Cache getInstance() {
if (instance == null) {
synchronized (Cache.class) {
if (instance == null) {
instance = new Cache();
}
}
}
return instance;
}
public Object get(String key) {
if (data.containsKey(key)) {
return data.get(key);
} else {
Object value = getValueFromDatabase(key);
data.put(key, value);
return value;
}
}
public void set(String key, Object value) {
data.put(key, value);
updateValueInDatabase(key, value);
}
private Object getValueFromDatabase(String key){
// 从数据库中获取 key 对应的数据
return null;
}
private void updateValueInDatabase(String key, Object value){
// 更新数据库中 key 对应的数据
}
}
在这个例子中,我们使用 Cache 类来保存缓存数据,并且使用 getInstance() 方法获取单例。
示例二:网络请求
在移动应用程序中,我们可能会遇到需要向服务器发送网络请求并获得响应的场景。在这种情况下,我们可以使用一个带有请求和响应方法的 Singleton 类来实现网络请求。
public class Network {
private static Network instance;
private OkHttpClient okHttpClient;
private Network() {
this.okHttpClient = new OkHttpClient();
}
public static Network getInstance() {
if (instance == null) {
synchronized (Network.class) {
if (instance == null) {
instance = new Network();
}
}
}
return instance;
}
public String getRequest(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
Response response = okHttpClient.newCall(request).execute();
return response.body().string();
}
public String postRequest(String url, RequestBody requestBody) throws IOException {
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
Response response = okHttpClient.newCall(request).execute();
return response.body().string();
}
}
在这个例子中,我们使用 Network 类来发送网络请求,并且使用 getInstance() 方法获取单例。
总结
Java双重检查加锁单例模式是单例模式的一种实现方式,它通过双重检查加锁的方式避免了多个实例同时创建的问题,并在执行效率上有所提高。在多线程环境下,双重检查加锁单例模式是一个值得一试的方案。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java双重检查加锁单例模式的详解 - Python技术站