设计模式-单例模式
# 单例模式概念
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。 Java中的Runtime即是单例。
# 单例的实现方式
# 饿汉式
Runtime类也是使用的饿汉式。
//饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
2
3
4
5
6
7
8
# 懒汉式(线程不安全)
//懒汉模式
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2
3
4
5
6
7
8
9
10
11
12
# 证明不安全
这种方法会存在一个问题就是,在并发情况下无法保证单例。比如两个线程同时运行到 if (Instance == null) 的时候,这个时候因为对象并未实例化,所以都是得到true.这个时候就会创建两个对象了,不能保证单例。
public class Test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(SingleTon.getInstance());
}
}, "线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(SingleTon.getInstance());
}
}, "线程2").start();
}
}
//多运行几次得到
SingleTon@6d0ca4c7
SingleTon@455d5a7e
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 饿汉式 方法锁(线程安全、效率低)
因为每次执行都是同步的,所以效率比较低
public class SingleTon {
private SingleTon(){};
private static SingleTon Instance;
public synchronized static SingleTon getInstance() {
if (Instance == null) {
Instance = new SingleTon();
}
return Instance;
}
}
2
3
4
5
6
7
8
9
10
11
# 饿汉式 Double CheckLock 双重校验锁(线程安全、效率高)
public class SingleTon {
private SingleTon(){};
private volatile static SingleTon Instance;
public static SingleTon getInstance() {
if (Instance == null) {
synchronized (SingleTon.class) {
if (Instance == null) {
Instance = new SingleTon();
}
}
}
return Instance;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
需要注意 Instance 采⽤ volatile 关键字修饰也是很有必要。 Instance 采⽤ volatile 关键字修饰也是很有必要的, Instance = new Singleton(); 这段代码其实是分为三步执⾏: 1. 分配内存空间 2. 初始化 3. 将Instance指向分配的内存地址(这个时候已经不为null了)
但是由于 JVM 具有指令重排的特性,执⾏顺序有可能变成 1→3→2。 指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例。 例如,线程 T1 执⾏了 1 和 3,此时 T2 调⽤ getInstance() 后发现 Instance 不为空,因此返回Instance, 但此时 Instance 还未被初始化。使⽤ volatile 可以禁⽌ JVM 的指令重排,保证在多线程环境下也能正常运⾏。 总结:如果不使用volatile关键词修饰,可能会导致拿到的对象是未被初始化的。
# 饿汉式 静态内部类
通过JVM来保证线程安全,高效
public class SingleTon {
private SingleTon() {
}
private static class SingleTonHolder {
private static final SingleTon INSTANCE = new SingleTon();
}
public static SingleTon getInstances() {
return SingleTonHolder.INSTANCE;
}
}
2
3
4
5
6
7
8
9
10
11
# 枚举方式
public enum SingleTonEnum {
/**
* 实例
*/
INSTANCE;
public void doSomething() {
//todo
}
}
2
3
4
5
6
7
8
9
10
# 如何选用
单例对象 占用资源少,不需要延时加载,枚举 好于 饿汉
单例对象 占用资源多,需要延时加载,静态内部类 好于 懒汉式