设计模式--单例模式

单例模式介绍

** 意图 **:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

** 主要解决 **:一个全局使用的类频繁地创建与销毁。

** 何时使用 **:当您想控制实例数目,节省系统资源的时候。

** 如何解决 **:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

** 关键代码 **:构造函数是私有的。

** 优点 **:

  1. 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  2. 避免对资源的多重占用(比如写文件操作)。

** 缺点 **:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

** 使用场景 **:

  1. 要求生产唯一序列号。
  2. WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  3. 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。

实现方式

饿汉式(静态常量)【可用】

** 代码实现 **:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 饿汉式(静态常量)【可用】
* 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
* 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
*/
public class Hungry {

//自有永久的对象
private static final Hungry hungry = new Hungry();

//构造器私有化
private Hungry() {
System.out.println("饿汉式(静态常量)...");
}

public static Hungry getInstance() {
return hungry;
}
}

** 测试类 **:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 单例模式测试
*/
public class singletonTest {

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
/**
* 饿汉式(静态常量)【可用】
*/
Hungry hungry = Hungry.getInstance();
System.out.println("第" + (finalI + 1) + "次获得的hungry对象的hashCode:" + hungry.hashCode());
}).start();
}
}
}

** 输出结果 **:
hungry

饿汉式(静态代码块)【可用】

** 代码实现 **:

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 main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 饿汉式(静态代码块)【可用】
* 将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。
* 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
* 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
*/
public class Hungry2 {

private static Hungry2 hungry2;

static {
hungry2 = new Hungry2();
}

private Hungry2() {
System.out.println("饿汉式(静态代码块)...");
}

public static Hungry2 getInstance() {
return hungry2;
}
}

** 测试类 **:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 单例模式测试
*/
public class singletonTest {

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
/**
* 饿汉式(静态代码块)【可用】
*/
Hungry2 hungry2 = Hungry2.getInstance();
System.out.println("第" + (finalI + 1) + "次获得的hungry2对象的hashCode:" + hungry2.hashCode());
}).start();
}
}
}

** 输出结果 **:
hungry2

懒汉式(线程不安全)【不可用】

** 代码实现 **:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 懒汉式(线程不安全)【不可用】
* 这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。
* 如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,
* 这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
*/
public class Lazy {

private static Lazy lazy;

private Lazy() {
System.out.println("懒汉式(线程不安全)...");
}

public static Lazy getInstance() {
if (lazy == null) {
lazy = new Lazy();
}
return lazy;
}
}

** 测试类 **:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 单例模式测试
*/
public class singletonTest {

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
/**
* 懒汉式(线程不安全)【不可用】
*/
Lazy lazy = Lazy.getInstance();
System.out.println("第" + (finalI + 1) + "次获得的lazy对象的hashCode:" + lazy.hashCode());
}).start();
}
}
}

** 输出结果 **:
lazy

懒汉式(线程安全,同步方法)【不推荐用】

** 代码实现 **:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 懒汉式(线程安全,同步方法)【不推荐用】
* 有点:解决了线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。
* 缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。
* 而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。
*/
public class Lazy2 {

private static Lazy2 lazy;

private Lazy2() {
System.out.println("懒汉式(线程安全,同步方法)...");
}

public static synchronized Lazy2 getInstance() {
if (lazy == null) {
lazy = new Lazy2();
}
return lazy;
}
}

** 测试类 **:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 单例模式测试
*/
public class singletonTest {

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
/**
* 懒汉式(线程安全,同步方法)【不推荐用】
*/
Lazy2 lazy2 = Lazy2.getInstance();
System.out.println("第" + (finalI + 1) + "次获得的lazy2对象的hashCode:" + lazy2.hashCode());
}).start();
}
}
}

** 输出结果 **:
lazy2

懒汉式(线程安全 , 同步代码块)【不可用】

** 代码实现 **:

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 main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 懒汉式(线程安全 , 同步代码块)【不可用】
* 由于同步方法的同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块。
* 但是这种同步并不能起到线程同步的作用。假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
*/
public class Lazy3 {

private static volatile Lazy3 lazy;

private Lazy3() {
System.out.println("懒汉式(线程安全,同步代码块)...");
}

public static Lazy3 getInstance() {
if (lazy == null) {
synchronized (Lazy3.class) {
lazy = new Lazy3();
}
}
return lazy;
}
}

** 测试类 **:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 单例模式测试
*/
public class singletonTest {

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
/**
* 懒汉式(线程安全,同步代码块)【不可用】
*/
Lazy3 lazy3 = Lazy3.getInstance();
System.out.println("第" + (finalI + 1) + "次获得的lazy3对象的hashCode:" + lazy3.hashCode());
}).start();
}
}
}

** 输出结果 **:
lazy3

静态内部类【推荐用】

** 代码实现 **:

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 main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 静态内部类【推荐用】
* 这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
* <p>
* 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
* <p>
* 优点:避免了线程不安全,延迟加载,效率高。
*/
public class StaticInnerClass {

private StaticInnerClass() {
System.out.println("静态内部类...");
}

private static class SingletonInstance {
private static StaticInnerClass instance = new StaticInnerClass();
}

public static StaticInnerClass getInstance() {
return SingletonInstance.instance;
}
}

** 测试类 **:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 单例模式测试
*/
public class singletonTest {

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
/**
* 静态内部类【推荐用】
*/
StaticInnerClass staticInnerClass = StaticInnerClass.getInstance();
System.out.println("第" + (finalI + 1) + "次获得的staticInnerClass对象的hashCode:" + staticInnerClass.hashCode());
}).start();
}
}
}

** 输出结果 **:
静态内部类

双重检查【推荐用】

** 代码实现 **:

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 main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 双重检查【推荐用】
* 我们进行了两次if (doubleCheck == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (doubleCheck == null),直接return实例化对象。
* 优点:线程安全;延迟加载;效率较高。
*/
public class DoubleCheck {

private static volatile DoubleCheck doubleCheck;

private DoubleCheck() {
System.out.println("双重检查...");
}

public static DoubleCheck getInstance() {
if (doubleCheck == null) {
synchronized (DoubleCheck.class) {
if (doubleCheck == null) {
doubleCheck = new DoubleCheck();
}
}
}
return doubleCheck;
}
}

** 测试类 **:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 单例模式测试
*/
public class singletonTest {

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
/**
* 双重检查【推荐用】
*/
DoubleCheck doubleCheck = DoubleCheck.getInstance();
System.out.println("第" + (finalI + 1) + "次获得的doubleCheck对象的hashCode:" + doubleCheck.hashCode());
}).start();
}
}
}

** 输出结果 **:
双重检查

枚举【推荐用】

** 代码实现 **:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 枚举【推荐用】
* 不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
*/
public enum EnumSingleton {
Instance;

public void whatEverMethod() {
}
}

** 测试类 **:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main.java.com.study.designPatterns.single;

/**
* @author: whb
* @description: 单例模式测试
*/
public class singletonTest {

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
/**
* 双重检查【推荐用】
*/
DoubleCheck doubleCheck = DoubleCheck.getInstance();
System.out.println("第" + (finalI + 1) + "次获得的doubleCheck对象的hashCode:" + doubleCheck.hashCode());
}).start();
}
}
}

** 输出结果 **:
枚举

CAS【不推荐用】

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
28
29
30
package com.springboot.whb.study.netty;

/**
* @author: whb
* @description: 单例模式CAS实现
* 优点:不需要使用传统的锁机制来保证线程安全,CAS 是一种基于忙等待的算法,依赖底层硬件的实现,相对于锁它没有线程切换和阻塞的额外消耗,可以支持较大的并行度。
* 缺点:如果忙等待一直执行不成功(一直在死循环中),会对 CPU 造成较大的执行开销。而且,这种写法如果有多个线程同时执行 singleton = new Singleton(); 也会比较耗费堆内存。
*/
public class Singleton {
// AtomicReference 是原子引用类型
private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();

private Singleton() {
}

public static Singleton getInstance() {
for (; ; ) {
Singleton instance = INSTANCE.get();
if (instance != null) {
return instance;
}
instance = new Singleton();
// CAS 方法有两个参数 expect 和 update,以原子方式实现了比较并设置的功能
// 如果当前值等于 expect,则更新为 update 并返回 true;否则不更新并返回 false
if (INSTANCE.compareAndSet(null, instance)) {
return instance;
}
}
}
}

Lock机制【推荐用】

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.springboot.whb.study.netty;

/**
* @author: whb
* @description: 类似双重检查
*/
public class Singleton {
private static Singleton instance = null;
private static Lock lock = new ReentrantLock();


private Singleton() {
}


public static Singleton getInstance() {
if (instance == null) {
lock.lock(); // 显式调用,手动加锁
if (instance == null) {
instance = new Singleton();
}
lock.unlock(); // 显式调用,手动解锁
}
return instance;
}
}

本文标题:设计模式--单例模式

文章作者:王洪博

发布时间:2018年06月13日 - 21:06

最后更新:2019年11月04日 - 08:11

原始链接:http://whb1990.github.io/posts/6d67519e.html

▄︻┻═┳一如果你喜欢这篇文章,请点击下方"打赏"按钮请我喝杯 ☕
0%