1. Overview
1.概述
In this tutorial, we’ll discuss the Bill Pugh Singleton implementation. There are several implementations of the Singleton Pattern. Notably, Lazy Loaded Singleton and Eager Loaded Singleton implementations are the prominent ones. Furthermore, these also support synchronized and non-synchronized versions as well.
在本教程中,我们将讨论 Bill Pugh Singleton 的实现。单例模式有多种实现。值得注意的是,Lazy Loaded Singleton 和 Eager Loaded Singleton 实现是其中的佼佼者。此外,这些实现还支持同步和非同步版本。
The Bill Pugh Singleton implementation supports lazy-loaded singleton object. In the upcoming sections, we’ll explore its implementation and see how it resolves the challenges faced by other implementations.
Bill Pugh Singleton 实现支持懒加载单例对象。在接下来的章节中,我们将探讨它的实现,看看它是如何解决其他实现所面临的挑战的。
2. Basic Principles of Singleton Implementation
2.实现单例的基本原则
Singleton is a creational design pattern. As the name suggests, this design pattern helps create a single instance of a class. This instance is used throughout the application. It’s often used for classes that are expensive and time-consuming to create, such as Connection Factories, REST Adapters, Dao, etc. classes.
Singleton 是一种娱乐设计模式。顾名思义,这种设计模式有助于创建类的单个实例。该实例将在整个应用程序中使用。它通常用于创建成本高、耗时长的类,如 Connection Factories、REST Adapters、Dao 等类。
Before we move on, let’s first look at the basic principles of singleton implementation of a Java class:
在继续之前,我们先来看看 Java 类单例实现的基本原理:
- Private constructors to prevent instantiation with the new operator
- A public static method preferably with the name getInstance() to return a single instance of the class
- A private static variable to store the only instance of the class
Furthermore, there could be challenges restricting a single instance of a class in a multi-threaded environment and deferring the initialization of the instance till it’s referred. Hence that’s where one implementation scores better than the others. Keeping these challenges in mind, we’ll see how the Bill Pugh Singleton implementation emerges as the winner.
此外,在多线程环境中限制类的单个实例,并将实例的初始化推迟到被引用时进行,也可能会遇到困难。因此,这正是一种实现优于其他实现的地方。考虑到这些挑战,我们将看看 Bill Pugh Singleton 实现是如何胜出的。
3. Bill Pugh Singleton Implementation
3.Bill Pugh Singleton Implementation
Mostly, Singleton implementations face one or both of the following challenges:
大多数情况下,Singleton 实现都会面临以下一个或两个挑战:
- Eager loading
- Overhead due to synchronization
The Bill Pugh or Holder Singleton pattern addresses both of them with the help of a private static inner class:
Bill Pugh 或 Holder Singleton 模式借助私有静态内部类解决了这两个问题:
public class BillPughSingleton {
private BillPughSingleton() {
}
private static class SingletonHelper {
private static final BillPughSingleton BILL_PUGH_SINGLETON_INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance() {
return SingletonHelper.BILL_PUGH_SINGLETON_INSTANCE;
}
}
The class loader in a Java application loads the static inner class SingletonHelper in the memory only once, even if multiple threads call getInstance(). Noticeably, we’re not using synchronized as well. This eliminates the overhead of locking and unlocking objects while accessing the synchronized methods. So this approach addresses the flaws faced by other implementations.
Java 应用程序中的类加载器只在内存中加载一次静态内部类 SingletonHelper ,即使多个线程调用 getInstance() 也是如此。值得注意的是,我们也没有使用 同步。这消除了在访问同步方法时锁定和解锁对象的开销。因此,这种方法解决了其他实现所面临的缺陷。
Now, let’s see how it works:
现在,让我们看看它是如何工作的:
@Test
void givenSynchronizedLazyLoadedImpl_whenCallgetInstance_thenReturnSingleton() {
Set<BillPughSingleton> setHoldingSingletonObj = new HashSet<>();
List<Future<BillPughSingleton>> futures = new ArrayList<>();
ExecutorService executorService = Executors.newFixedThreadPool(10);
Callable<BillPughSingleton> runnableTask = () -> {
logger.info("run called for:" + Thread.currentThread().getName());
return BillPughSingleton.getInstance();
};
int count = 0;
while(count < 10) {
futures.add(executorService.submit(runnableTask));
count++;
}
futures.forEach(e -> {
try {
setHoldingSingletonObj.add(e.get());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
});
executorService.shutdown();
assertEquals(1, setHoldingSingletonObj.size());
}
In the above method, multiple threads call getInstance() concurrently. However, it always returns the same object reference.
在上述方法中,多个线程同时调用 getInstance() 。但是,它总是返回相同的对象引用。
4. Bill Pugh vs Unsynchronized Implementations
4. 比尔-普格与非同步实施
Let’s implement the Singleton Pattern for both single-threaded and multi-threaded environments. We’ll avoid using the synchronized keyword for the implementation in the multi-threaded environment.
让我们同时在单线程和多线程环境中实现单例模式。我们将避免使用 synchronized 关键字来实现多线程环境。
4.1. Lazy-Loaded Singleton Implementation
4.1.懒加载单例实现
Based upon the basic principles described earlier, let’s implement a singleton class:
根据前面描述的基本原则,让我们来实现一个单例类:
public class LazyLoadedSingleton {
private static LazyLoadedSingleton lazyLoadedSingletonObj;
private LazyLoadedSingleton() {
}
public static LazyLoadedSingleton getInstance() {
if (null == lazyLoadedSingletonObj) {
lazyLoadedSingletonObj = new LazyLoadedSingleton();
}
return lazyLoadedSingletonObj;
}
}
LazyLoadedSingleton object gets created only when the getInstance() method is called. This is called lazy initialization. However, this fails as a result of a dirty read when multiple threads concurrently call the getInstance() method. This isn’t the case with Bill Pugh’s implementation even without the use of synchronized.
LazyLoadedSingleton 对象仅在调用 getInstance() 方法时创建。这就是所谓的懒初始化。然而,当多个线程同时调用 getInstance() 方法时,由于脏读的结果,这种初始化会失败。在 Bill Pugh 的实现中,即使不使用 synchronized. 也不会出现这种情况。
Let’s see if the class LazyLoadedSingleton creates only a single object:
让我们看看类 LazyLoadedSingleton 是否只创建了一个对象:
@Test
void givenLazyLoadedImpl_whenCallGetInstance_thenReturnSingleInstance() throws ClassNotFoundException {
Class bs = Class.forName("com.baledung.billpugh.LazyLoadedSingleton");
assertThrows(IllegalAccessException.class, () -> bs.getDeclaredConstructor().newInstance());
LazyLoadedSingleton lazyLoadedSingletonObj1 = LazyLoadedSingleton.getInstance();
LazyLoadedSingleton lazyLoadedSingletonObj2 = LazyLoadedSingleton.getInstance();
assertEquals(lazyLoadedSingletonObj1.hashCode(), lazyLoadedSingletonObj2.hashCode());
}
The above method tries to instantiate LazyLoadedSingleton with the help of reflection API and by calling getInstance() method. However, instantiation fails with reflection, and getInstance() always returns a single instance of the class LazyLoadedSingleton.
上述方法试图借助 reflection API 并通过调用 getInstance() 方法来实例化 LazyLoadedSingleton 。但是,使用反射时实例化失败,getInstance() 总是返回类 LazyLoadedSingleton 的单个实例。
4.2. Eager-Loaded Singleton Implementation
4.2.急于加载的单例实现
The implementation discussed in the earlier section works only in a single-threaded environment. However, for a multi-threaded environment, we can consider a different approach using a class-level static variable:
前一节讨论的实现方法仅适用于单线程环境。不过,对于多线程环境,我们可以考虑使用类级静态变量的不同方法:
public class EagerLoadedSingleton {
private static final EagerLoadedSingleton EAGER_LOADED_SINGLETON = new EagerLoadedSingleton();
private EagerLoadedSingleton() {
}
public static EagerLoadedSingleton getInstance() {
return EAGER_LOADED_SINGLETON;
}
}
The class level variable EAGER_LOADED_SINGLETON is static. Hence, when the application starts, it loads it immediately even when it’s not needed. However, as discussed earlier, Bill Pugh’s implementation supports lazy loading in both single and multi-threaded environments.
类级变量 EAGER_LOADED_SINGLETON 是静态的。因此,当应用程序启动时,即使不需要它,也会立即加载它。不过,正如前面所讨论的,Bill Pugh 的实现支持单线程和多线程环境下的懒加载。
Let’s see the EagerLoadedSingleton class in action:
让我们来看看 EagerLoadedSingleton 类的实际应用:
@Test
void givenEagerLoadedImpl_whenCallgetInstance_thenReturnSingleton() {
Set<EagerLoadedSingleton> set = new HashSet<>();
List<Future<EagerLoadedSingleton>> futures = new ArrayList<>();
ExecutorService executorService = Executors.newFixedThreadPool(10);
Callable<EagerLoadedSingleton> runnableTask = () -> {
return EagerLoadedSingleton.getInstance();
};
int count = 0;
while(count < 10) {
futures.add(executorService.submit(runnableTask));
count++;
}
futures.forEach(e -> {
try {
set.add(e.get());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
});
executorService.shutdown();
assertEquals(1, set.size());
}
In the above method, multiple threads call runnableTask. Then, the run() method calls getInstance() to fetch an instance of EagerLoadedSingleton. However, every time getInstance() returns a single instance of the object.
在上述方法中,多个线程调用 runnableTask. 然后,run() 方法调用 getInstance() 以获取 EagerLoadedSingleton 的实例。但是,每次 getInstance() 返回的都是该对象的一个实例。
The above works in a multi-threaded environment but it exhibits eager loading, which is clearly a drawback.
上述方法可在多线程环境中运行,但会出现急于加载的情况,这显然是一个缺点。
5. Bill Pugh vs Synchronized Singleton Implementation
5.Bill Pugh vs 同步单例实现
Earlier, we saw LazyLoadedSingleton in a single-threaded environment. Let’s modify it to support singleton pattern in a multi-threaded environment:
前面,我们看到了单线程环境中的 LazyLoadedSingleton 。让我们对其进行修改,以便在多线程环境中支持单例模式:
public class SynchronizedLazyLoadedSingleton {
private static SynchronizedLazyLoadedSingleton synchronizedLazyLoadedSingleton;
private SynchronizedLazyLoadedSingleton() {
}
public static synchronized SynchronizedLazyLoadedSingleton getInstance() {
if (null == synchronizedLazyLoadedSingleton) {
synchronizedLazyLoadedSingleton = new SynchronizedLazyLoadedSingleton();
}
return synchronizedLazyLoadedSingleton;
}
}
Interestingly, by using the synchronized keyword on the method getInstance(), we restrict threads from accessing it concurrently. We can achieve a more performant variant of this using the double-checked locking method.
有趣的是,通过在方法 getInstance() 上使用 synchronized 关键字,我们限制了线程并发访问该方法。我们可以使用 双重检查锁定方法实现性能更高的变体。
However, Bill Pugh’s implementation is clearly a winner because it can be used in a multi-threaded environment without the overhead of synchronization.
不过,Bill Pugh 的实现显然更胜一筹,因为它可以在多线程环境中使用,而无需同步开销。
Let’s confirm if this works in a multi-threaded environment:
让我们来确认一下这是否能在多线程环境下工作:
@Test
void givenSynchronizedLazyLoadedImpl_whenCallgetInstance_thenReturnSingleton() {
Set<SynchronizedLazyLoadedSingleton> setHoldingSingletonObj = new HashSet<>();
List<Future<SynchronizedLazyLoadedSingleton>> futures = new ArrayList<>();
ExecutorService executorService = Executors.newFixedThreadPool(10);
Callable<SynchronizedLazyLoadedSingleton> runnableTask = () -> {
logger.info("run called for:" + Thread.currentThread().getName());
return SynchronizedLazyLoadedSingleton.getInstance();
};
int count = 0;
while(count < 10) {
futures.add(executorService.submit(runnableTask));
count++;
}
futures.forEach(e -> {
try {
setHoldingSingletonObj.add(e.get());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
});
executorService.shutdown();
assertEquals(1, setHoldingSingletonObj.size());
}
Just like EagerLoadedSingleton, the SynchronizedLazyLoadedSingleton class also returns a single object in a multi-threaded setup. But this time the program loads the singleton object in a lazy fashion. However, it comes with an overhead because of synchronization.
与 EagerLoadedSingleton 类一样,SynchronizedLazyLoadedSingleton 类也会在多线程设置中返回单个对象。但这一次,程序是以一种懒散的方式加载单例对象的。不过,由于同步的原因,这也会带来一定的开销。
6. Conclusion
6.结论
In this article, we compared the Bill Pugh Singleton Implementation with other prevalent singleton implementations. Bill Pugh Singleton’s implementation performs better and supports lazy loading. Hence, many applications and libraries use it widely.
在本文中,我们将 Bill Pugh Singleton 实现与其他流行的单例实现进行了比较。Bill Pugh Singleton 的实现性能更好,而且支持懒加载。因此,许多应用程序和库都广泛使用它。
As usual, the code used in this article can be found over on GitHub.