单例模式
Singleton
饿汉
1 | public class Singleton { |
线程安全,但是可能造成资源的浪费
懒汉 双重校验锁 DCL
- 懒汉(延迟初始化) 要素:无参构造私有,进行判断「线程不安全」
1 | public class Singleton { |
- 原来的实现容易出现线程安全问题,改进:
getInstance()
变成synchronized
方法。锁范围太大:性能差,同步开销大。正常的执行路径不需要同步,只需要在创建实例的时候进行锁定即可。
锁对象的选择:两个不相关的方法不要使用同一把锁。并且要注意不能出现死锁互相等待。
class:适用于保护静态变量或者类级别的共享资源,如果是为了保护某个对象的状态,就应该使用this。
this可能的问题:这边在内部使用this调用task.dosth(),另一边一个完全无关的业务使用 task做锁。
死锁:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class Friend {
private final String name;
public Friend(String name) {this.name = name;}
public void bow(Friend other) {
synchronized (this) {
System.out.println(this.name + " 正在向 " + other.name + " 鞠躬");
try {
Thread.sleep(100); // 模拟执行延迟
} catch (InterruptedException e) {}
other.bowBack(this);
}
}
public void bowBack(Friend other) {
synchronized (this) {
System.out.println(this.name + " 正在回礼 " + other.name);
}
}
}
// 死锁:a和b同时向对方bow,先都锁住了自己,bowBack的时候就会造成互相等待
未二次判空:线程 A 通过了条件判断,获取了锁,开始创建实例,就在同时,线程B也通过判断等待锁释放,在线程 A 出来之后线程B又进去创建了一次。
指令重排序:Java 允许指令重排序,创建对象可以分为 1. 分配内存地址 2. 在这块内存地址创建对象 3. 将 对象的引用 instance 指向这块地址。如果2 3的顺序颠倒,线程 A 进去之后在完全创建对象之前就将 instance 置为 非null,线程B就会以为别人已经创建好了,直接返回 instance 引用,线程B拿到以后就开始使用,其中就会随机发生空指针或者状态异常等其他难以复现调试的bug。引入 volatile,表示这个变量不允许指令重排,且线程对它的写入会立刻对其他线程可见。避免发生拿到错误对象的情况
1 | public class Singleton { |
适用场景:
- 单例模式:延迟初始化全局唯一的对象。
- 高并发资源初始化:如数据库连接池、线程池等。
- 框架中的扩展点加载:如 Dubbo 的
ExtensionLoader
按需加载扩展类。
双重校验锁 (与类解耦)
1 | public class Holder<T>{ |
枚举 enum
1 | public enum Singleton { |
天然线程安全,并且能够防止反射攻击
Holder 静态内部类 (利用类加载机制)
1 | public class Singleton { |
Holder 在 Singleton 加载时并不会被加载,只有第一次调用 getInstance()
才会加载 Holder,然后创建单例。
CAS 自旋
1 | import java.util.concurrent.atomic.AtomicReference; |
无锁,非阻塞,线程安全。实现较为复杂,追求极致性能才考虑