介绍
Java Singleton Pattern(Java Singleton Pattern)是四种设计模式(Gangs of Four Design Patterns)中的一个,属于创意设计模式
(Creational Design Pattern)类别,从定义上看,它似乎是一个简单的设计模式,但当涉及到实施时,它带来了很多担忧。
在本文中,我们将学习关于singleton设计模式的原则,探索实施singleton设计模式的不同方式,以及其使用的一些最佳实践。
Singleton 模式原则
- Singleton 模式限制了类的实例化,并确保在 Java 虚拟机中只存在一个类的实例
- Singleton 类必须提供一个全球接入点来获取类的实例
- Singleton 模式用于 logging,驱动程序对象,缓存和 thread pool
- Singleton 设计模式也用于其他设计模式,如 [Abstract Factory]/community/tutorials/abstract-factory-design-pattern-in-java), [Builder](
Java Singleton 模式实施
要实现单曲模式,我们有不同的方法,但它们都有以下共同的概念。
- 私有构建器将类的实例化限制在其他类
- 同一类的私有静态变量是该类的唯一实例
- 返回类的实例的公共静态方法,这是外部世界获取单一类实例的全球访问点
在进一步的部分中,我们将学习不同方法来实现singleton模式和设计问题。
1、初始化
在急迫的初始化中, singleton 类的实例是在类加载时创建的。 急迫的初始化的缺点是,即使客户端应用程序可能不使用它,该方法也会创建。
1package com.journaldev.singleton;
2
3public class EagerInitializedSingleton {
4
5 private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
6
7 // private constructor to avoid client applications using the constructor
8 private EagerInitializedSingleton(){}
9
10 public static EagerInitializedSingleton getInstance() {
11 return instance;
12 }
13}
如果您的 singleton 类不使用大量的资源,这是使用的方法,但在大多数情况下, singleton 类用于资源,如文件系统,数据库连接等,我们应该避免实例化,除非客户端调用getInstance
方法。
静态区块初始化
静态块初始化实现类似于渴望初始化,但类的实例是在提供例外处理
选项的静态块中创建的(/community/tutorials/exception-handling-in-java Java Exception Handling Tutorial with Examples and Best Practices
)。
1package com.journaldev.singleton;
2
3public class StaticBlockSingleton {
4
5 private static StaticBlockSingleton instance;
6
7 private StaticBlockSingleton(){}
8
9 // static block initialization for exception handling
10 static {
11 try {
12 instance = new StaticBlockSingleton();
13 } catch (Exception e) {
14 throw new RuntimeException("Exception occurred in creating singleton instance");
15 }
16 }
17
18 public static StaticBlockSingleton getInstance() {
19 return instance;
20 }
21}
热切初始化和静态块初始化都在使用之前创建实例,这不是最佳的做法。
3、初始化
用于实现 singleton 模式的懒惰初始化方法在全球访问方法中创建实例. 以下是使用此方法创建 singleton 类的示例代码:
1package com.journaldev.singleton;
2
3public class LazyInitializedSingleton {
4
5 private static LazyInitializedSingleton instance;
6
7 private LazyInitializedSingleton(){}
8
9 public static LazyInitializedSingleton getInstance() {
10 if (instance == null) {
11 instance = new LazyInitializedSingleton();
12 }
13 return instance;
14 }
15}
在单线程环境的情况下,前面的实现工作很好,但当涉及到多线程系统时,如果多个线程同时处于如果
状态时,它可能会导致问题。
第三章 安全单曲
创建一个无线单一字符类的简单方法是将全球访问方法( / community / tutorials / thread-safety-in-java "Java Synchronization and Thread Safety Tutorial with Examples")进行同步,这样一次只有一个线程才能执行此方法。
1package com.journaldev.singleton;
2
3public class ThreadSafeSingleton {
4
5 private static ThreadSafeSingleton instance;
6
7 private ThreadSafeSingleton(){}
8
9 public static synchronized ThreadSafeSingleton getInstance() {
10 if (instance == null) {
11 instance = new ThreadSafeSingleton();
12 }
13 return instance;
14 }
15
16}
之前的实现工作顺利,并提供线程安全性,但由于与同步方法相关的成本,它降低了性能,尽管我们只需要它对于可能创建单独实例的前几个线程。 为了避免每一次这种额外的过头,使用了 double-checked locking 原则。 在这种方法中,同步区块在如果
条件内使用,并额外检查以确保只创建单个实例。
1public static ThreadSafeSingleton getInstanceUsingDoubleLocking() {
2 if (instance == null) {
3 synchronized (ThreadSafeSingleton.class) {
4 if (instance == null) {
5 instance = new ThreadSafeSingleton();
6 }
7 }
8 }
9 return instance;
10}
继续你的学习与( / 社区 / 教程 / 线程 - 安全 - 在java - 单人 - 类)。
比尔·普希·辛格尔顿实施
在 Java 5 之前,Java 内存模型出现了很多问题,以前的方法在某些场景中失败,当时太多的线程试图同时获得 singleton 类的实例,所以 Bill Pugh提出了一个不同的方法来使用 内部静态辅助类创建 singleton 类。
1package com.journaldev.singleton;
2
3public class BillPughSingleton {
4
5 private BillPughSingleton(){}
6
7 private static class SingletonHelper {
8 private static final BillPughSingleton INSTANCE = new BillPughSingleton();
9 }
10
11 public static BillPughSingleton getInstance() {
12 return SingletonHelper.INSTANCE;
13 }
14}
注意包含 singleton 类的实例的 _private 内部静态类。当 singleton 类被加载时,‘SingletonHelper’ 类不会被加载到内存中,只有当有人呼叫getInstance() 方法时,这个类会被加载并创建 singleton 类实例。
使用反思来摧毁Singleton模式
反射可以用来摧毁所有以前的singleton实现方法. 以下是示例类:
1package com.journaldev.singleton;
2
3import java.lang.reflect.Constructor;
4
5public class ReflectionSingletonTest {
6
7 public static void main(String[] args) {
8 EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
9 EagerInitializedSingleton instanceTwo = null;
10 try {
11 Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
12 for (Constructor constructor : constructors) {
13 // This code will destroy the singleton pattern
14 constructor.setAccessible(true);
15 instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
16 break;
17 }
18 } catch (Exception e) {
19 e.printStackTrace();
20 }
21 System.out.println(instanceOne.hashCode());
22 System.out.println(instanceTwo.hashCode());
23 }
24
25}
当你运行之前的测试课时,你会注意到这两个实例的hashCode
不是相同的,它破坏了单一的模式。反射非常强大,并在许多框架中使用,如春季和冬眠。继续你的学习与Java反射教程(/社区/教程/java反射示例教程Java反射教程类,方法,领域,建设者,注释和更多
) 。
第7章 单身
为了克服这种情况,Joshua Bloch(https://en.wikipedia.org/wiki/Joshua_Bloch)建议使用enum
来实现独特的设计模式,因为Java确保任何enum
值只在Java程序中实例化一次,因为Java Enum
(/community/tutorials/java-enum Java Enum Examples with Benefits and Class usage
)的值是全球可访问的,所以singleton
也是如此。
1package com.journaldev.singleton;
2
3public enum EnumSingleton {
4
5 INSTANCE;
6
7 public static void doSomething() {
8 // do something
9 }
10}
8、序列化与单曲
有时在分布式系统中,我们需要在 singleton 类中实施Serializable
接口,以便我们可以在文件系统中存储其状态,并在稍后时刻获取它。
1package com.journaldev.singleton;
2
3import java.io.Serializable;
4
5public class SerializedSingleton implements Serializable {
6
7 private static final long serialVersionUID = -7604766932017737115L;
8
9 private SerializedSingleton(){}
10
11 private static class SingletonHelper {
12 private static final SerializedSingleton instance = new SerializedSingleton();
13 }
14
15 public static SerializedSingleton getInstance() {
16 return SingletonHelper.instance;
17 }
18
19}
对序列单字符类的一个问题是,每次我们将其 deserialize 时,它会创建一个新的类实例。
1package com.journaldev.singleton;
2
3import java.io.FileInputStream;
4import java.io.FileNotFoundException;
5import java.io.FileOutputStream;
6import java.io.IOException;
7import java.io.ObjectInput;
8import java.io.ObjectInputStream;
9import java.io.ObjectOutput;
10import java.io.ObjectOutputStream;
11
12public class SingletonSerializedTest {
13
14 public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
15 SerializedSingleton instanceOne = SerializedSingleton.getInstance();
16 ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
17 "filename.ser"));
18 out.writeObject(instanceOne);
19 out.close();
20
21 // deserialize from file to object
22 ObjectInput in = new ObjectInputStream(new FileInputStream(
23 "filename.ser"));
24 SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
25 in.close();
26
27 System.out.println("instanceOne hashCode="+instanceOne.hashCode());
28 System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());
29
30 }
31
32}
此代码产生此输出:
1[secondary_label Output]
2instanceOne hashCode=2011117821
3instanceTwo hashCode=109647522
为了克服这种情况,我们只需要提供readResolve()
方法的实施。
1protected Object readResolve() {
2 return getInstance();
3}
在此之后,您会注意到测试程序中两种实例的hashCode
是相同的。
阅读关于 Java Serialization和 Java Deserialization。
结论
本文涵盖了Singleton设计模式。
继续学习更多的Java教程(https://andsky.com/tags/java)。