1 界说

有时刻为了节约系统资源,需要确保系统中某个类只有唯一一个实例,当这个实例建立乐成之后,无法再建立一个同类型的其他工具,所有的操作都只能基于这个唯一的实例,这是单例模式的念头所在。

单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局接见的方式。单例模式是一种工具建立型模式。

2 要点

  • 某个类只能有一个实例
  • 它必须自行建立这个实例
  • 它必须自行向整个系统提供这个实例

3 通用步骤

一般来说把一个通俗类重构为一个单例类需要以下三步:

  • 组织函数私有化:也就是克制外部直接使用new等建立工具
  • 界说一个静态类成员保留实例
  • 增添一个类似getInstance()的公有静态方式来获取实例

因此一般来说单例模式的结构图如下:

4 实例

下面以一个简化的负载均衡器设计举行单例模式的说明。
某个软件需要使用一个全局唯一的负载均衡器,设计如下:

public class LoadBalancer
{
    private static LoadBalancer instance = null;

    private LoadBalancer(){}

    public static LoadBalancer getInstance()
    {
        return instance == null ? instance = new LoadBalancer() : instance;
    }

    public static void main(String[] args) {
        LoadBalancer balancer1 = LoadBalancer.getInstance();
        LoadBalancer balancer2 = LoadBalancer.getInstance();
        System.out.println(balancer1 == balancer2);
    }
}

这是最简朴的单例类的设计,获取实例时仅仅判断是否为null,没有思量到线程问题。也就是说,多个线程同时获取实例时,照样会发生多个实例,一般来说,常见的解决方式如下:

  • 饿汉式单例
  • 懒汉式单例
  • IoDH

5 饿汉式单例

饿汉式单例就是在通俗的单例类基础上,在界说静态变量时就直接实例化,因此在类加载的时刻就已经建立了单例工具,而且在获取实例时不需要举行判空操作直接返回实例即可:

public class LoadBalancer
{
    private static LoadBalancer instance = new LoadBalancer();

    private LoadBalancer(){}

    public static LoadBalancer getInstance()
    {
        return instance;
    }
}

当类被加载时,静态变量instance被初始化,类的私有组织方式将被挪用,单例类的唯一实例将被建立。

6 懒汉式单例

懒汉式单例在类加载时不举行初始化,在需要的时刻再初始化,加载实例,同时为了制止多个线程同时挪用getInstance(),可以加上synchronized

public class LoadBalancer
{
    private static LoadBalancer instance = null;

    private LoadBalancer(){}

    synchronized public static LoadBalancer getInstance()
    {
        return instance == null ? instance = new LoadBalancer() : instance;
    }
}

这种手艺又叫延迟加载手艺,只管解决了多个线程同时接见的问题,然则每次挪用时都需要举行线程锁定判断,这样会降低效率。事实上,单例的焦点在于instance = new LoadBalancer(),因此只需要锁定这行代码,优化如下:

,

联博统计

www.tianyulinmo.com采用以太坊区块链高度哈希值作为统计数据,联博以太坊统计数据开源、公平、无任何作弊可能性。联博统计免费提供API接口,支持多语言接入。

,
public static LoadBalancer getInstance()
{
    if(instance == null)
    {
        synchronized (LoadBalancer.class)
        {
            instance = new LoadBalancer();
        }
    }
    return instance;
}

然则实际情况中照样有可能泛起多个实例,由于若是A和B两个线程同时挪用getInstance(),都通过了if(instance == null)的判断,假设线程A先获得锁,建立实例后,A释放锁,接着B获取锁,再次建立了一个实例,这样照样导致发生多个单例工具,因此,通常接纳一种叫“双重检查锁定”的方式来确保不会发生多个实例:

private volatile static LoadBalancer instance = null;
public static LoadBalancer getInstance()
{
    if(instance == null)
    {
        synchronized (LoadBalancer.class)
        {
            if(instance == null)
            {
                instance = new LoadBalancer();
            }
        }
    }
    return instance;
}

需要注重的是要使用volatile修饰变量,volatile可以保证可见性以及有序性。

7 饿汉式与懒汉式的对照

  • 饿汉式单例在类加载时就已经初始化,优点在于无需思量多线程接见问题,可以确保实例的唯一性
  • 从挪用速率方面来说会优于懒汉式单例,由于在类加载时就已经被建立
  • 从资源行使效率来说饿汉式单例会劣于懒汉式,由于无论是否需要使用都市加载单例工具,而且由于加载时需要建立实例会导致类加载时间变长
  • 懒汉式单例实现了延迟加载,无须一直占用系统资源
  • 懒汉式单例需要处置多线程并发接见问题,需要双重检查锁定,且通常来说初始化历程需要较长时间,会增大多个线程同时首次挪用的几率,这会导致系统性能受一定影响

    8 IoDH

    为了战胜饿汉式单例不能延迟加载以及懒汉式单例的线程平安控制繁琐问题,可以使用一种叫Initialization on Demand Holder(IoDH)的手艺。实现IoDH时,需在单例类增添一个静态内部类,在该内部类中建立单例工具,再将该单例工具通过getInstance()方式返回给外部使用,代码如下:

    public class LoadBalancer
    {
    private LoadBalancer(){}
    private static class HolderClass
    {
        private static final LoadBalancer instance = new LoadBalancer();
    }
    
    public static LoadBalancer getInstance()
    {
        return HolderClass.instance;
    }
    }

    由于单例工具没有作为LoadBalancer的成员变量直接实例化,因此类加载时不会实例化instance。首次挪用getInstance()时,会初始化instance,由JVM保证线程平安性,确保只能被初始化一次。另外相比起懒汉式单例,getInstance()没有线程锁定,因此性能不会有任何影响。
    通过IoDH既可以实现延迟加载,又可以保证线程平安,不影响系统性能,然则瑕玷是与编程语言自己的特征相关,许多面向工具语言不支持IoDH。另外,还可能引发NoClassDefFoundError(当初始化失败时),例子可以戳这里。

9 枚举实现单例(推荐)

其中,无论是饿汉式,照样懒汉式,照样IoDH,都有或多或少的问题,而且还可以通过反射以及序列化/反序列化方式去“强制”天生多个单例,有没有更优雅的解决方案呢?
有!谜底就是枚举。
代码如下:

public class Test
{
    public static void main(String[] args) {
        LoadBalancer balancer1 = SingletonEnum.INSTANCE.getInstance();
        LoadBalancer balancer2 = SingletonEnum.INSTANCE.getInstance();
        System.out.println(balancer1 == balancer2);
    }
}

class LoadBalancer
{
}

enum SingletonEnum
{
    INSTANCE;
    private LoadBalancer instance = null;
    private SingletonEnum()
    {
        instance = new LoadBalancer();
    }

    public LoadBalancer getInstance()
    {
        return instance;
    }
}

10 总结

10.1 优点

单例模式主要优点如下:

  • 单例模式提供了对唯一实例的受控接见,可以严格控制客户怎样以及何时接见它
  • 由于在系统内存中只存在一个工具,因此可以节约系统资源,对于一些需要频仍建立和销毁的工具,单例模式可以提高系统性能
  • 可以扩展成多例类

10.2 瑕玷

单例模式主要瑕玷如下:

  • 没有抽象层,扩展难题
  • 单例类职责过重,一定程度上违反了单一权责原则,由于既提供了营业方式,也提供了建立工具方式,将工具建立以及工具自己的功效耦合在一起
  • 许多语言提供了GC机制,实例化的工具长时间不使用将被接纳,下次使用需要重新实例化,这回导致共享的单例工具状态丢失

10.3 使用场景

  • 系统需要一个实例工具
  • 客户挪用类的单个实例只允许使用一个公共接见点

10.4 头脑导图总结