Java 单例设计模式最佳实践(附示例

介绍

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 SerializationJava Deserialization

结论

本文涵盖了Singleton设计模式。

继续学习更多的Java教程(https://andsky.com/tags/java)。

Published At
Categories with 技术
comments powered by Disqus