常用设计模式系列(六)—单例模式

一、前言

各位大佬好,由于本人的原因,拖更了几天休息了一下,吃了点好吃的,为了填饱自己的肚子,逛遍了天下美食(我所在这个城市的某个角落),所谓干饭人干饭魂,干饭人吃饭得用盆。

img

吃饱喝足之后,活还是要干的,所以今天继续开始我们的设计模式,今天讲解设计模式之“单例模式”,单例模式算的上是在整个设计模式体系中最为简单的模式,它依然属于创建型分类的对象模式。目前较为流行的Spring,将项目中的对象进行了管理,其bean工厂使用的设计模式就是单例模式,我来详解下单例模式。

二、单例模式的概念

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

表现形式:

1、单例类只能有一个实例。

2、单例类必须自己创建自己的唯一实例。

3、单例类必须给所有其它对象提供这一实例。

个人理解:

单例模式的主要表现为:单例模式创建的类在整个系统中全局唯一,不能够被多次创建,并且只能通过自己创建自己的实例。

场景举例:

在使用电脑操作excel文件时,如果别的程序已经打开了这个excel文件,系统会提示这个文件就已经被其它程序占用,从而不能够操作这个文件,创建出来的这个文件就是全局唯一的,如果别人想使用,那么需要我操作完毕之后,下一个人才能使用。

uml图

img

三、代码实现

1.创建单例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.yang.Singleton;

/**
* @ClassName Singleton
* @Description 单例代码
* @Author IT小白架构师之路
* @Date 2020/12/15 19:09
* @Version 1.0
**/
public class Singleton {
//类初始化时创建对象
private static Singleton singleton;

//把构造方法访问权限改为私有,防止多次创建对象
private Singleton(){

}
//定义获取对象的静态方法
public static Singleton getSingleton(){
if(null == singleton){
singleton = new Singleton();
}
return singleton;
}
}

2.创建客户端测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.yang.Singleton;

/**
* @ClassName Clent
* @Description 注释
* @Author IT小白架构师之路
* @Date 2020/12/15 19:14
* @Version 1.0
**/
public class Client {
public static void main(String[] args) {
//创建对象
Singleton singleton = Singleton.getSingleton();
//创建对象
Singleton singleton1 = Singleton.getSingleton();
System.out.println(singleton == singleton1);
}
}

3.结果测试

1
true

经过测试,发现是单例对象没错,两个对象的地址是一样的。此时我们的程序都是在单个线程下执行的,如果进行多线程测试,创建对象实例会不会是线程安全的呢?写个代码测试下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1.编写Runnbale
package com.yang.Singleton;

import sun.jvm.hotspot.runtime.Thread;

/**
* @ClassName SingletonThread
* @Description 注释
* @Author IT小白架构师之路
* @Date 2020/12/15 19:55
* @Version 1.0
**/
public class SingletonRunble implements Runnable {
@Override
public void run(){
try {
Singleton singleton = Singleton.getSingleton();
java.lang.Thread.sleep(100);
System.out.println(singleton);
}catch (Exception e){
e.printStackTrace();
}

}
}

2.编写测试类,创建11个线程进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.yang.Singleton;

import java.util.HashSet;
import java.util.Set;

/**
* @ClassName SingletonThreadClient
* @Description 注释
* @Author IT小白架构师之路
* @Date 2020/12/15 19:57
* @Version 1.0
**/
public class SingletonThreadClient {
public static void main(String[] args) throws Exception{
for (int i = 0; i<=10 ; i++){
Runnable runnable = new SingletonRunble();
Thread thread = new Thread(runnable);
thread.start();
}
Thread.sleep(5000);
}
}

3.测试结果如下:

1
2
3
4
5
6
7
8
9
10
11
对象地址:com.yang.Singleton.Singleton@266a1d19
对象地址:com.yang.Singleton.Singleton@266a1d19
对象地址:com.yang.Singleton.Singleton@266a1d19
对象地址:com.yang.Singleton.Singleton@266a1d19
对象地址:com.yang.Singleton.Singleton@266a1d19
对象地址:com.yang.Singleton.Singleton@266a1d19
对象地址:com.yang.Singleton.Singleton@680cc142
对象地址:com.yang.Singleton.Singleton@266a1d19
对象地址:com.yang.Singleton.Singleton@298c37fd
对象地址:com.yang.Singleton.Singleton@266a1d19
对象地址:com.yang.Singleton.Singleton@266a1d19

测试结论发现,此种创建对象的单例模式,在多线程使用的场景下,出现了线程安全问题,使用的的并非是同一个对象,如何解决呢?我会通过讲解几种单例模式创建方式,来验证每种单例模式创建方式是否会有线程安全问题。
概念:

  • 懒汉式:比较慵懒的方式,只有需要的时候我才去生成对象。

  • 饿汉式:提前生成对象,但是所有人只能用一种对象

  • 饱汉式:此种方式不是单例模式,就是每次需要都生成新的对象,默认的构造就是这种方式。

单例模式创建的几种方式

1.非线程安全的懒汉式(上方已经验证过的创建方式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.yang.Singleton;

/**
* @ClassName SingletonLh
* @Description 懒汉式-非线程安全方式
* @Author IT小白架构师之路
* @Date 2020/12/15 19:33
* @Version 1.0
**/
public class SingletonLh {

//类初始化时创建对象
private static SingletonLh singletonLh;

//把构造方法访问权限改为私有,防止多次创建对象
private SingletonLh() {
}

//获取对象
public static SingletonLh getInstance() {
if (singletonLh == null) {
singletonLh = new SingletonLh();
}
return singletonLh;
}
}

2.加锁后线程安全的懒汉式

2.1 代码编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.yang.Singleton;

/**
* @ClassName SingletonLhSafe
* @Description 懒汉式-线程安全方式
* @Author IT小白架构师之路
* @Date 2020/12/15 17:37
* @Version 1.0
**/
public class SingletonLhSafe {
//类初始化时创建对象
private static SingletonLhSafe singletonLhSafe;

//把构造方法访问权限改为私有,防止多次创建对象
private SingletonLhSafe() {
}

//获取对象
public synchronized static SingletonLhSafe getInstance() {
if (singletonLhSafe == null) {
singletonLhSafe = new SingletonLhSafe();
}
return singletonLhSafe;
}
}

2.2测试结果所知,为线程安全的

1
2
3
4
5
6
7
8
9
10
11
对象地址:com.yang.Singleton.SingletonLhSafe@6ebc1cfd
对象地址:com.yang.Singleton.SingletonLhSafe@6ebc1cfd
对象地址:com.yang.Singleton.SingletonLhSafe@6ebc1cfd
对象地址:com.yang.Singleton.SingletonLhSafe@6ebc1cfd
对象地址:com.yang.Singleton.SingletonLhSafe@6ebc1cfd
对象地址:com.yang.Singleton.SingletonLhSafe@6ebc1cfd
对象地址:com.yang.Singleton.SingletonLhSafe@6ebc1cfd
对象地址:com.yang.Singleton.SingletonLhSafe@6ebc1cfd
对象地址:com.yang.Singleton.SingletonLhSafe@6ebc1cfd
对象地址:com.yang.Singleton.SingletonLhSafe@6ebc1cfd
对象地址:com.yang.Singleton.SingletonLhSafe@6ebc1cfd

3. 饿汉式(线程安全)

3.1代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.yang.Singleton;

/**
* @ClassName SingletonEh
* @Description 线程安全的饿汉式
* @Author IT小白架构师之路
* @Date 2020/12/15 17:44
* @Version 1.0
**/
public class SingletonEh {
//类初始化时创建对象
private static SingletonEh singletonEh = new SingletonEh();

//把构造方法访问权限改为私有,防止多次创建对象
private SingletonEh() {
}

//获取对象
public static SingletonEh getInstance() {
return singletonEh;
}
}

3.2 测试结果,为线程安全

1
2
3
4
5
6
7
8
9
10
11
 对象地址:com.yang.Singleton.SingletonEh@4952fb3e
对象地址:com.yang.Singleton.SingletonEh@4952fb3e
对象地址:com.yang.Singleton.SingletonEh@4952fb3e
对象地址:com.yang.Singleton.SingletonEh@4952fb3e
对象地址:com.yang.Singleton.SingletonEh@4952fb3e
对象地址:com.yang.Singleton.SingletonEh@4952fb3e
对象地址:com.yang.Singleton.SingletonEh@4952fb3e
对象地址:com.yang.Singleton.SingletonEh@4952fb3e
对象地址:com.yang.Singleton.SingletonEh@4952fb3e
对象地址:com.yang.Singleton.SingletonEh@4952fb3e
对象地址:com.yang.Singleton.SingletonEh@4952fb3e

4.提高性能的volatile关键字方式的双检锁/双重锁方式,从编译器级别指定此对象共享。

4.1 代码编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.yang.Singleton;

/**
* @ClassName SingletonDoubleLocl
* @Description 提高性能的双检锁方式
* @Author IT小白架构师之路
* @Date 2020/12/15 20:19
* @Version 1.0
**/
public class SingletonDoubleLock {
//类初始化时创建对象,使用volatile关键字
private volatile static SingletonDoubleLock singletonDoubleLock;

//把构造方法访问权限改为私有,防止多次创建对象
private SingletonDoubleLock() {
}

//获取对象
public synchronized static SingletonDoubleLock getInstance() {
if (singletonDoubleLock == null) {
synchronized(Singleton.class){
singletonDoubleLock = new SingletonDoubleLock();
}
}
return singletonDoubleLock;
}
}

4.2 验证结果:为线程安全的

1
2
3
4
5
6
7
8
9
10
11
对象地址:com.yang.Singleton.SingletonDoubleLock@266a1d19
对象地址:com.yang.Singleton.SingletonDoubleLock@266a1d19
对象地址:com.yang.Singleton.SingletonDoubleLock@266a1d19
对象地址:com.yang.Singleton.SingletonDoubleLock@266a1d19
对象地址:com.yang.Singleton.SingletonDoubleLock@266a1d19
对象地址:com.yang.Singleton.SingletonDoubleLock@266a1d19
对象地址:com.yang.Singleton.SingletonDoubleLock@266a1d19
对象地址:com.yang.Singleton.SingletonDoubleLock@266a1d19
对象地址:com.yang.Singleton.SingletonDoubleLock@266a1d19
对象地址:com.yang.Singleton.SingletonDoubleLock@266a1d19
对象地址:com.yang.Singleton.SingletonDoubleLock@266a1d19

5.静态内部类的方式(内部类初始化对象)

5.1代码编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.yang.Singleton;

/**
* @ClassName SingletonInside
* @Description 静态内部类的方式
* @Author IT小白架构师之路
* @Date 2020/12/15 20:33
* @Version 1.0
**/
public class SingletonInside {
//构造函数私有化
private SingletonInside(){
}
//定义静态内部类
private static class SingletonInsideClass{
private static final SingletonInside SINGLETON_INSIDE = new SingletonInside();
}
//返回对象方法
public static final SingletonInside getInstance(){
return SingletonInsideClass.SINGLETON_INSIDE;
}
}

5.2验证结果,静态内部类创建的方式也是线程安全的

1
2
3
4
5
6
7
8
9
10
11
对象地址:com.yang.Singleton.SingletonInside@24b0c0a
对象地址:com.yang.Singleton.SingletonInside@24b0c0a
对象地址:com.yang.Singleton.SingletonInside@24b0c0a
对象地址:com.yang.Singleton.SingletonInside@24b0c0a
对象地址:com.yang.Singleton.SingletonInside@24b0c0a
对象地址:com.yang.Singleton.SingletonInside@24b0c0a
对象地址:com.yang.Singleton.SingletonInside@24b0c0a
对象地址:com.yang.Singleton.SingletonInside@24b0c0a
对象地址:com.yang.Singleton.SingletonInside@24b0c0a
对象地址:com.yang.Singleton.SingletonInside@24b0c0a
对象地址:com.yang.Singleton.SingletonInside@24b0c0a

6.枚举方式的创建,枚举也是目前比较主流的方式

6.1代码测试

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.yang.Singleton;
/**
* @ClassName SingletonEh
* @Description 枚举的方式创建
* @Author IT小白架构师之路
* @Date 2020/12/15 20:44
* @Version 1.0
**/
public enum SingletonEmu {
SINGLETON_EMU;
private SingletonEmu(){
}
}

6.2验证结果,每次都是自己,创建对象过程为线程安全的

1
2
3
4
5
6
7
8
9
10
11
对象地址:SINGLETON_EMU
对象地址:SINGLETON_EMU
对象地址:SINGLETON_EMU
对象地址:SINGLETON_EMU
对象地址:SINGLETON_EMU
对象地址:SINGLETON_EMU
对象地址:SINGLETON_EMU
对象地址:SINGLETON_EMU
对象地址:SINGLETON_EMU
对象地址:SINGLETON_EMU
对象地址:SINGLETON_EMU

四、单例模式优缺点及适用场景

优点:

1.在单例模式中,所有需要的对象实例,这个实例只有一个,减少了内存的消耗,也可以减少频繁创建销毁实例的过程(Spring框架应用的比较优秀)。

2.可以减少对资源的多重占用,减少线程安全问题,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作

3.单例模式可以在系统设置全局的访问点,优化和共享资源访问

缺点:

1.单例模式没有抽象层,不可以扩展,需要扩展则需要修改源码。

2.单例模式下,对象的职责会很重

3.不能应用于线程池、连接池相关场景,因为全局只有一个连接对象。

适用场景:

1.需要进行内存优化,防止系统频繁创建对象的场景。

2.进行文件修改,防止其他程序对此程序同时修改造成脏数据场景。

3.需要进行全局设置的变量访问点,用来共享资源时

4.线程间相互通信时。