0%

单例模式

单例模式的介绍、原理、常用的实现方式。

单例模式

介绍:单例模式的类只有单个对象会被创建(实例化),向其他类只提供一种访问方式。可以全局访问.

解决了什么:只能有一个实例操作资源。多个打印任务,正在打印的实例只存在一个。

优点:减小了频繁创建和销毁类(GC)的系统开销。多线安全的单例模式解决了多线程问题。

哪里用到了:要求只有一个对象的场景.Controller类中的Service类可用单例模式创建对象。Spring用单例模式管理注入容器的类。

如何实现:使用一个私有静态变量、一个私有构造函数、以及一个公有静态函数来实现。

Implementation(具体实现):

Singleton Implementation - UML Class Diagram

1.懒汉式

优点:支持lazy loading 。私有静态变量 instance被延迟实例化,节约资源。

缺点:不支持多线程。多线程环境下是不安全的,如果多个线程同时进入 if (instance == null) ,并且此时instance 为 null,会实例化多次instance。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {  
private static Singleton instance;
//私有化构造函数
private Singleton (){}

//对外提供唯一访问方法
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

2.饿汉式:

类加载(classloader)就实例化 instance,避免了多线程问题,无lazy loading 的效果。
优点:支持多线程。

缺点:类加载时就实例化 instance,而不是使用时实例化instance,浪费内存。

1
2
3
4
5
6
7
8
public class Singleton { 
private static Singleton instance = new Singleton();
private Singleton (){}

public static Singleton getInstance() {
return instance;
}
}

3.双重校验锁

优点:支持多线程,有lazy loading效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {
//volatile防止JVM编译器指令重排
private volatile static Singleton instance;
private Singleton() {}

public static Singleton getinstance() {
if (instance == null) {
//对实例化的代码加锁
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

第一个if (instance == null),instance没有被实例化时,对实例化内容加锁。避免非必要加锁。

第二个if (instance == null),第一次实例化instance时,防止多个线程在synchronized处等待并执行instance = new Singleton()

volatile 修饰instance的原因: JVM 具有指令重排的特性,多线程环境下会导致一个线程获得还没有初始化的实例。volatile可禁止JVM指令重排。

具体描述:

instance = new Singleton();

实例化对象的代码分为三个指令执行:

  1. 为 instance 分配内存空间

  2. 初始化 instance

  3. 将 instance 指向分配的内存地址

    线程 T1 执行了 1 和 3时,线程T2获取instance,T2会获得一个不该为null的instance。

    volatile广泛存在于java并发系列中。

4.静态内部类

优点: 支持lazy loading,多线程安全。实现简单。

缺点: 只适用于静态域的情况。(菜鸟教程提到)

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {

private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}

private Singleton (){}

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

private static class SingletonHolder静态内部类不会初始化对象,多次调用getInstance()得到的是同一个对象INSTANCE。

5.枚举:

缺点: 无lazy loading。
优点: 自动支持序列化机制,可防止反射,能避免多线程同步问题。

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
public class User {
//私有化构造函数
private User(){ }

//定义一个静态枚举类
static enum SingletonEnum{
//创建一个枚举对象,该对象天生为单例
INSTANCE;
private User user;
//私有化枚举的构造函数
private SingletonEnum(){
user=new User();
}
public User getInstnce(){
return user;
}
}

//对外暴露一个获取User对象的静态方法
public static User getInstance(){
return SingletonEnum.INSTANCE.getInstnce();
}
}

public class Test {
public static void main(String [] args){
System.out.println(User.getInstance());
System.out.println(User.getInstance());
System.out.println(User.getInstance()==User.getInstance());
}
}
结果为true

未完成: 静态内部类只适用于静态域,枚举如何避免多线程和反序列化,未防止反射破解单例。