单例模式是经典的设计模式之一。什么是设计模式?代码的设计模式类似于棋谱,棋谱就是一些下棋的固定套路,是前人总结出来的一些固定的打法。依照棋谱来下棋,不说能下得非常好,但至少是有迹可循,不会下得很糟糕。代码的设计模式也是一样。
设计模式,就是软件开发中的棋谱。一些编程界的大佬,针对一些常见情景总结出了一些代码的“编写套路”。按照这样的套路来写代码,不说能写得非常好,但也至少不会写得太糟糕。以前有一个大佬写了一本书,名叫《讨论二十三种设计模式》,这本书广为流传,这里的设计模式也就是我们上面说到的。
事实上设计模式远不止“二十三种”。以下两种设计模式经常遇到:
单例模式
工厂模式
本文主要介绍单例模式。
一、什么是单例模式?
单例指的就是单个实例(instance),也就是单个对象(对象就是类的实例)。单例模式指的是某个类在进程中只有唯一一个实例(在一个程序中,只能创建一个实例(一个对象),不能创建多个对象)。
按理来说,在写代码的时候多 new 几次,就能创建多个对象了。但在语法上,是有办法禁止这样多 new 几次的操作的。
也就是说,Java中的单例模式,实际上是借助 Java 语法,保证某个类只能够创建出一个实例,而不能被new多次。
为什么会有这样的用途?其实原因是很简单的:在有些场景下,本身它就要求某个概念是单例的。比如每个人只能同时拥有一个配偶。
二、如何实现单例模式?
Java实现单例模式的方式有很多种,这里我们主要介绍两种写法:
饿汉模式(急迫)
懒汉模式(从容)
如何理解 饿汉模式 和 懒汉模式 呢?饿汉模式就好比每次吃完饭之后,立刻就把碗给洗了(主打的就是一个急迫);懒汉模式则是每次吃完饭了,先把碗放到一边先不洗,等到吃下一顿了再洗。通常认为,懒汉模式更好,效率更高(非必要不洗碗)。
比如,中午吃饭用了4个碗,那么饿汉模式就得一次性把4个碗都洗了;而晚上吃饭要用2个碗,懒汉模式就只需要洗4个碗当中用不到的2个碗就行了。洗2个碗明显要比洗4个碗效率更高(不考虑没洗的碗会变臭~~只考虑效率)。
在计算机中的例子:打开一个硬盘上的文件,读取文件内容并显示出来。
饿汉:把文件所有内容都读到内存中,并显示。
懒汉:只把文件读一小部分,把当前屏幕填充上。如果用户翻页了,再读其它文件内容;如果不翻页,就省下了。
在这样的情况下,懒汉模式也是完胜饿汉模式的。
假设要读取的文件非常大,有 10G,按照饿汉模式的方式,文件打开可能都要卡半天,更何况还有内存是否足够的问题。
但懒汉模式下就可以很快速地打开,因为它只读取 1 页的内容,1 页也就几百字,可能也就读取 2k 就够了;如果用户还要读其它页的内容,懒汉再从内存里读取相应的内容,用户浏览不到的页面,也就不将它的内容加载到内存中了。
(虽然懒汉模式会增加硬盘的读取次数,但和饿汉模式的情况相比,是不值一提的。)
下面我们来看看如何用代码实现这两种单例模式。
1、代码实现:饿汉模式
a.饿汉模式的构造思路
我们先初步地创建出 Singleton 类,并在里面把对象创建出来:
// 把一个类设置成单例的 class Singleton { // 唯一实例的本体 private static Singleton instance = new Singleton(); // 把对象创建出来 // 获取到实例的方法 public static Singleton getInstance() { return instance; } }
注意:这里的 instance 属性要用 static 修饰,static变量保存了单例对象的唯一实例。
同时,将 instance 属性用private封装,并提供一个get方法。这样,我们就可以从外部获取instance了:
public class Test { public static void main(String[] args) { // 此时 s1 和 s2 是同一个对象 Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); } }
显然,此处的 s1 和 s2 获取到的实际是同一个对象。
但是,上述的代码并没有限定再次 new 对象的操作:
此处的 s3 也显然与 s1 和 s2 不是同一个对象。因此,此处必须把 new 操作给禁止掉。采用的方式是 构造方法私有化。
将构造方法用 private 修饰,可以发现,此时我们上面的 new 操作就报错了,无法通过编译。
有些同学可能会想到,用反射仍然可以获取到私有方法。一方面,反射本身就是一种非常规的手段,它本身就是不安全的;另一方面,单例模式有一种实现方式,借助枚举,也可以保证反射下的安全,这个在此不过多介绍。
b.总结:饿汉模式代码
class Singleton { private static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; } private Singleton(){ } } public class Test { public static void main(String[] args) { //此时s1和s2是同一个对象 Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); } }
将一个类设置成单例的
通过Java语法来限制类实例的多次创建,从而实现单例模式:
static是用于类级别的数据共享,保存了单例对象的唯一实例,可以在单例模式中用来单例实例的唯一性。
单例模式中将构造方法私有化,可以避免外部直接创建新的实例。
但饿汉模式的有一个问题,那就是实例的创建时机过早了。只要类一加载,就会创建出这个实例,可要是后面并没有用到这个实例呢?
更好的实现方式是懒汉模式。
2、代码实现:懒汉模式
懒汉模式的核心思想:非必要,不创建。懒汉模式和饿汉模式的代码实现类似,最大的区别是饿汉模式在不使用instance对象时,不把它new出来。
以下代码就是懒汉模式的实现:
class SingletonLazy { //先令instance引用为null private static SingletonLazy instance = null; //获取instance实例 public static SingletonLazy getInstance() { if(instance == null) { instance = new SingletonLazy(); } return instance; } //构造方法私有化 private SingletonLazy() { } }
Java多线程基础-8:单例模式及其线程安全问题(二)+
https://developer.aliyun.com/article/1520525?spm=a2c6h.13148508.setting.14.75194f0eujcMau