单例模式浅谈 ——饿汉式及懒汉式的Java实现

常常工做面试的时候,面试官会问,怎么保证某个类在程序运行过程当中只有一个对象存在?面试

以上问题,实际就是面试官在了解面试者对单例模式的了解。缓存

一.为何须要单例模式

以常见的使用计算机打印文件为例,假设计算机拥有A,B两台链接的打印机,如后台打印程序也有两个的话,可能形成以下问题:安全

当打印文档的时候,由于打印程序也有多个,可能形成一份完整文档同时被两个打印程序同时进行分配,致使A,B打印机互相不知道在打印什么,致使混乱。多线程

因此,每台计算机能够有若干个打印机,但只能有一个后台打印程序,以免两个打印做业同时输出到打印机中。每台计算机能够有若干通讯端口,系统应当集中管理这些通讯端口,以免一个通讯端口同时被两个请求同时调用。并发

 在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具备资源管理器的功能。app

总之,选择单例模式就是为了不不一致状态,避免政出多头。ide

二.单例模式的特色

1. 单例模式只能有一个实例。高并发

2. 单例类必须建立本身的惟一实例。spa

3. 单例类必须向其余对象提供这一实例。线程

三.单例模式的分类

1.饿汉式单例

2.懒汉式单例

3.登记式单例

登记式单例是为了解决模式1,2不能继承的问题而开发的,自己存在争议,暂不讨论。

四.单例模式的开发

1.线程安全的饿汉模式

1)既然在程序中只能建立一个对象,那也就是说在不能在其它类中任意建立对象,不然对象确定就不止一个,这就要求被建立那个类的构造方法不能public,也就是必须private,这个就保证了外部不能乱建立对象。

1.png


2)外面建立不了对象,也就只能在内部建立对象了,由于咱们讲究Java的封装特性,对象是private,因此咱们要提供一个public方法供外界调用。

2.png


3)由于在外面不可能建立对象,想调用方法就必须将方法static,这样就能够经过:类名.方法名获取对象了,又由于静态方法里只能用静态成员,因此single必须static化。

3.png


4)至此,建立一个对象的任务完成了,通常状况下,咱们另外在static加个final,

最终程序以下

4.png

饿汉式是线程安全的,在类建立的同时就已经建立好一个静态的对象供系统使用,之后不在改变无需关注多线程问题、写法简单明了、能用则用。

可是它是加载类时建立实例、因此若是是一个工厂模式、缓存了不少实例、那么就得考虑效率问题,由于这个类一加载则把全部实例无论用不用一块建立。

2.线程安全的懒汉式单例

5.png

之因此叫作懒汉模式,主要是由于此种方法能够很是明显的lazy loading,简单说就是何时用,何时建。

然而并发实际上是一种特殊状况,大多时候这个锁占用的额外资源都浪费了,这种方式写出来的结构效率很低。

3.双重校验锁法

解释一下在并发时,双重校验锁法会有怎样的情景:

STEP 1. 线程A访问getInstance()方法,由于单例尚未实例化,因此进入了锁定块。

STEP 2. 线程B访问getInstance()方法,由于单例尚未实例化,得以访问接下来代码块,而接下来代码块已经被线程1锁定。

STEP 3. 线程A进入下一判断,由于单例尚未实例化,因此进行单例实例化,成功实例化后退出代码块,解除锁定。

STEP 4. 线程B进入接下来代码块,锁定线程,进入下一判断,由于已经实例化,退出代码块,解除锁定。

STEP 5. 线程A初始化并获取到了单例实例并返回,线程B获取了在线程A中初始化的单例。

理论上双重校验锁法是线程安全的,而且,这种方法实现了lazyloading。

Volatile关键字的做用: 禁止进行指令的重排序

6.png


4.静态类内部加载

7.png

使用内部类的好处是,静态内部类不会在单例加载时就加载,而是在调用getInstance()方法时才进行加载,达到了相似懒汉模式的效果,而这种方法又是线程安全的。

5.很是优雅的枚举实现单例模式

8.png


Effective Java做者Josh Bloch 提倡的方式,简洁而完美。解决了如下三个问题:

(1)自由序列化。

(2)保证只有一个实例。

(3)线程安全。

若是咱们想调用它的方法时,仅须要如下操做:

9.png


五.总结一下:

推荐使用饿汉式和枚举实现单例模式,饿汉式彻底不用考虑线程安全,枚举则是简单优雅;懒汉式的问题则是当没有高并发的状况下,白白浪费资源,效率低下;并且,若是建立对象时初始化的工做较多的话,可能会将这部分时间转嫁给用户,不太友好。

相关文章
相关标签/搜索