小司机带你学习单例模式的六种姿势!

单例模式是创建型模式的一种,下面总结一下在 Java 中实现单例模式的几种方法,并在多线程环境中进行了测试。

一、单例模式概念

单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供了全局访问的方法。

单例模式的三个特点:

  • 这个类只能有一个实例。
  • 这个类自行创建这个实例。
  • 这个类自行向整个系统提供这个实例。

单例模式的应用有,Windows 里的任务管理器(一个系统中只有一个)、网站计数器(实现多个页面的计数同步)、数据库连接池(减少资源损耗)等。

二、单例模式实现

根据单例模式的特点,我们来实现单例模式,在类中提供一个静态方法来获取这个唯一的实例对象,给其他类提供实例,并且这个实例对象不能直接使用 new 创建,所以构造方法要声明成私有,这便是最简单的单例模式实现。这样的实现在单线程环境中,当然没问题,但是我们还要考虑多线程环境下的安全实现。

下面是对单例模式的各种实现,并且对每种实现方法都在多线程环境中做了测试,所有代码都在我的 GitHub 仓库中中,传送门。该仓库还在完成中,用 Java 实现 23 种设计模式,并对设计原则和每种设计模式做出详细的分析,感兴趣的可以 fork 或者 star 哦,也欢迎小伙伴参与该仓库的完成。

2.1 懒汉式单例类(线程不安全)

通过 getInstance() 方法得到单例对象,单例对象在需要的时候才被延迟创建,所以称之为懒汉式。但是在多线程环境中,由于这个 getInstance() 方法可能被多个线程同时调用,这很可能会创建多个实例,所以这种实现在多线程环境下是不安全的。

懒汉式-线程不安全.jpg

2.2 懒汉式单例类(线程安全)

给 getInstance() 加上 synchronized 关键字后,可以保证这个方法在同一时间只能被一个线程调用,多个线程调用这个方法要排队依次调用,这就保证了只会创建一个单例对象,在多线程环境下是安全的。

懒汉式-线程安全.jpg

2.3 饿汉式单例类

相比于上面的懒汉式,饿汉式在类加载的时候就会创建实例对象,在 getInstance() 方法直接返回创建好的对象,简单直接,在多线程环境下也是安全的。

饿汉式.jpg

2.4 双重校验锁单例类

针对于上面的线程安全的懒汉式加载,这种实现方式不是直接给方法加上 synchronized 关键字,而是在 getInstance() 方法做双重检查来解决线程不安全的问题。这种方式允许多个线程同时调用该方法,但是在方法中会进行两次检查,第一次检查实例是否已经存在,如果不存在才进入下面的同步代码块,线程安全的创建实例,如果实例真的不存在(避免这是有其他线程创建好了,再次创建新的实例)才会创建实例。这种方式理论上要比直接使用 synchronized 关键字性能要高,但是对于不同虚拟机对 volatile 关键字的优化,优势并不明显。

双重校验式.jpg

2.5 静态内部类单例类

创建一个静态内部类,来创建实例,和上面饿汉式相比,虽然都是直接 new 实例,但是这种方式在外部类加载时,静态内部类并不会被加载。只有在第一次调用 getInstance() 方法时,才会显式的加载静态内部类,创建实例,也是一种延迟(懒)创建方式。

静态内部类.jpg

2.6 枚举单例类

枚举实现单例模式是 Java 大牛们比较推荐的,因为这种方式实现非常简单,并且这种方式但是大多数单例模式的实现并不是这种方式。这种方式需要开发者对枚举有清晰的认识,这里也简单的回顾一下枚举的基本知识。

枚举是在 Java1.5 之后出现的,可以更加简单的定义常量,通过反编译,我们可以发现枚举其实也是一个 Java 类,这个类继承自 Enum 接口,定义的枚举对象会被加上 static final 关键字,这就是我们不用枚举时声明常量的方式,另外在 static 静态代码块中初始化枚举对象,枚举的构造方法被加上了 private 关键字,防止其他类创建新的枚举对象实例。虽然前面几种方式无法直接使用 new 创建新的实例,但是可以用反射来绕过 private 限制,而枚举却有自带的序列化机制、防止反射攻击造成多次实例化、线程安全的优点,从这些地方我们都可以看出使用枚举是实现单例模式的绝佳方式。

枚举式.jpg

三、单例模式总结

根据对资源加载时机的需要,来选择合适的单例模式实现方式,如果是懒加载方式,可以选择懒汉方式和双重校验锁方式;如果将资源加载的时间提前来达到使用时的快速体验,可以选择饿汉方式;如果涉及到序列化创建单例对象,可以选择枚举方式。

单例模式的优点是提供了对唯一实例的访问控制,可以节约系统资源,但缺点是单例类的职责过重,并且缺少抽象层难以扩展,不太符合单一职责原则。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ImportSource

并发编程-用锁来保护状态

由于锁机制可以让他保护起来的代码片段始终被串行访问。也就是一个访问完了,再由下一个来访问。我们可以利用锁的这种特点,来约定一些协议,来对共享的状态进行独占访问。...

3355
来自专栏JetpropelledSnake

Python设计模式之单例模式

  本系列文章是希望将软件项目中最常见的设计模式用通俗易懂的语言来讲解清楚,并通过Python来实现,每个设计模式都是围绕如下三个问题: 为什么?即为什么要使用...

38312
来自专栏黑泽君的专栏

day08_Servlet学习笔记

============================================================

561
来自专栏用户2442861的专栏

linux select函数详解

http://blog.csdn.net/lingfengtengfei/article/details/12392449

902
来自专栏禅林阆苑

mysql学习总结06 — SQL编程

事务(transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言书写的用户程序的执行所引起...

2104
来自专栏古时的风筝

从实例出发,了解单例模式和静态块

什么是单例模式呢,单例模式(Singleton)又叫单态模式,它出现目的是为了保证一个类在系统中只有一个实例,并提供一个访问它的全局访问点。从这点可以看出,单例...

790
来自专栏维C果糖

史上最简单的 MySQL 教程(四十)「数据库变量」

系统变量,顾名思义,是系统设置好的变量(皆为全局级别变量),也是用来控制服务器表现的,如autocommit、wait_timeout等。

43512
来自专栏猿人谷

realloc invalid pointer错误解析

realloc invalid pointer错误 char* temp=(char*) realloc(src,sizeof(char)*100); 如上面这...

1975
来自专栏黑泽君的专栏

c语言基础学习10_关于文件操作的复习

============================================================================= 如果...

870
来自专栏猛牛哥的博客

快手(AAU)更新记录v2.9.1.36

1033

扫码关注云+社区

领取腾讯云代金券