单例设计模式(Singleton)

Posted by 余腾 on 2019-08-02
Estimated Reading Time 8 Minutes
Words 2.1k In Total
Viewed Times

什么是单例设计模式?

单例设计模式 —> 创建型模式

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。
通过单例模式可以保证系统中,应用该模式的类只有一个实例。即一个类只有一个对象实例。

在java语言中,单例带来了两大好处:

(1)对于频繁使用的对象(数据源、Session工厂),可以省略创建对象所花费的时间,这对于重量级的对象而言,是非常可观的一笔系统开销。
(2)由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。

具体实现

  • 需要:
    (1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
    (2)在该类内部产生一个唯一的实例化对象,并且将其封装为 private static 类型。
    (3)定义一个静态方法返回这个唯一对象。

实现一:饿汉式 / 静态常量

  • 立即加载就是使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了)。常见的实现办法就是直接new实例化。
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
public class HungrySingletonStaticInstance {
public static void main(String[] args) {
System.out.println("======HungrySingletonStaticInstance======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}

class Singleton {

// 将自身实例化对象设置为一个属性,并用static、final修饰
private final static Singleton instance = new Singleton();

// 构造方法私有化
private Singleton() {}

// 提供静态方法返回该实例
public static Singleton getInstance() {
return instance;
}
}

“饿汉模式”的优缺点:

  • 优点:实现起来简单,没有多线程同步问题。
  • 缺点:当类 Singleton 被加载的时候,会初始化 static 的 instance,静态变量被创建并分配内存空间,从这以后,这个 static 的 instance 对象便一直占着这段内存,可能造成内存浪费(即便你还没有用到这个实例)。当类被卸载时,静态变量被摧毁,并释放所占有内存,在某些特定条件下会耗费内存。
  • 如果方法内有其他 static 方法,调用该方法此类也加载初始化。

实现二:饿汉式 / 静态代码块

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
public class HungrySingletonStaticBlock {
public static void main(String[] args) {
System.out.println("======HungrySingletonStaticBlock======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}

class Singleton {

private static Singleton instance;

static {
instance = new Singleton();
}

private Singleton() {}

public static Singleton getInstance() {
return instance;
}
}
  • 如果方法内有其他static方法,调用该方法此类也加载初始化。

实现三:懒汉式 / 线程不安全

  • 延迟加载就是调用 getInstance() 方法时实例才被创建(先不急着实例化出对象,等要用的时候才创建出来)。常见的实现方法就是在 getInstance() 方法中进行new实例化。
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
public class LazyLoadingSingletonThreadUnSafe {
public static void main(String[] args) {
System.out.println("======LazySingletonThreadUnsafe======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}

class Singleton {

// 将自身实例化对象设置为一个属性,并用static修饰
private static Singleton instance;

// 构造方法私有化
private Singleton() {}

// 静态方法返回该实例
public static Singleton getInstance() {
if (instance == null) {
//TODO 线程在这里被阻塞,则此时对象没有被创建,UnSafe
instance = new Singleton();
}
return instance;
}
}

懒汉模式”的优缺点:

  • 优点:实现起来比较简单,当类 Singleton 被加载的时候,静态变量 static 的 instance 未被创建并分配内存空间,当 getInstance() 方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存。

  • 缺点:在多线程环境中,这种实完现方法是全错误的,不能保证单例的状态。

  • 如果方法内有其他 static 方法,调用该方法此类不会加载初始化。


实现四:懒汉式 / 线程安全 Sync

  • 静态方法返回该实例,加 Synchronized 关键字实现同步。
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
public class LazyLoadingSingletonSync {
public static void main(String[] args) {
System.out.println("======LazyLoadingSingletonSync======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}

class Singleton {

// 将自身实例化对象设置为一个属性,并用static修饰
private static Singleton instance;

// 构造方法私有化
private Singleton() {}

// 静态方法返回该实例
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

线程安全的“懒汉模式”(加锁)的优缺点:

  • 优点:在多线程情形下,保证了“懒汉模式”的线程安全。

  • 缺点:在多线程情形下,synchronized方法通常效率低,显然这不是最佳的实现方案。

  • 如果方法内有其他static方法,调用该方法此类不会加载初始化。


实现五:DCL双检查锁机制

(DCL:Double Checked Locking)

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
31
32
33
34
public class LazyLoadingSingletonDCL {
public static void main(String[] args) {
System.out.println("======LazyLoadingSingletonDCL======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}

class Singleton {

// 将自身实例化对象设置为一个属性,并用 volatile、static 修饰
private volatile static Singleton instance;

// 构造方法私有化
private Singleton() {}

// 静态方法返回该实例
public static Singleton getInstance() {
// 第一次检查instance是否被实例化出来
if (instance == null) {
synchronized (Singleton.class) {
// 某个线程取得了类锁,实例化对象前第二次检查 instance 是否已经被实例化
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

方法五算是单例模式的最佳实现方式。内存占用率高,效率高,线程安全,多线程操作原子性。

  • 如果方法内有其他 static 方法,调用该方法此类不会加载初始化。

实现六:静态内部类

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
public class StaticInternalSingleton {
public static void main(String[] args) {
System.out.println("======StaticInternalSingleton======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}


class Singleton {

private Singleton() {}

public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}

private static class SingletonHolder {
//加载 Singleton 类时并不会加载内部类
private static final Singleton INSTANCE = new Singleton();
}
}
  • 第一次加载 Singleton 类时并不会加载内部类初始化 Instance,只有第一次调用 getInstance 方法时虚拟机加载 SingletonHolder 并初始化 Instance ,这样不仅能确保线程安全也能保证 Singleton类的唯一性,所以推荐使用静态内部类单例模式。

实现七:枚举单例

  • 这借助 JDK 1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class EnumSingleton {
public static void main(String[] args) {
System.out.println("======EnumSingleton======");
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());

instance1.method();
instance2.method();
}
}

enum Singleton {
INSTANCE;

public void method() {
System.out.println("EnumSingleton");
}
}

实现八:使用容器实现

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
31
32
import java.util.HashMap;
import java.util.Map;

public class ContainerOfMapKeySingleton {
public static void main(String[] args) {
System.out.println("======ContainerOfMapKeySingleton======");
Singleton.registerService("Singleton", new Object());
Object instance1 = Singleton.getService("Singleton");
Object instance2 = Singleton.getService("Singleton");

System.out.println("instance1 == instance2:" + (instance1 == instance2));//true

System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}

class Singleton {
private static Map<String, Object> objMap = new HashMap<>();

public Singleton() {}

public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}

public static Object getService(String key) {
return objMap.get(key);
}
}

利用了 HashMap 容器 key 不可重复的特性

  • 优点:这种实现方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一接口进行获取操作,降低用户使用成本,也对用户隐藏了具体实现,降低耦合度。
  • 缺点:没有私有化构造方法,用户可以 new 出新的实例对象。

JDK 中单例的应用 Runtime 类

1
2
3
4
5
6
7
8
public class Runtime {
private static Runtime currentRuntime = new Runtime();

public static Runtime getRuntime() {
return currentRuntime;
}
//TODO 私有化构造器
private Runtime() {}

感谢阅读


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !