1. 单例模式
单例模式是一种设计模式。它的核心思想是一个类在整个应用程序中只创建一个实例,并提供一个统一的访问接口
设计模式 : 应对常见问题的解决方案。假设女朋友经常生气,但每次用 道歉 + 买礼物 就能很快和女朋友和好
"道歉 + 买礼物" 在某种意义上就是设计模式。单例模式就是应对程序反复出现的问题的解决方案
单例模式最常见的 2 种实现方式 : 1. 懒汉模式 2. 饿汉模式
2. 饿汉模式
在类加载的时候,创建实例
java
class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
3. 懒汉模式
在使用的时候创建实例
java
class SingletonLazy{
private static SingletonLazy instance = null;
public static SingletonLazy getInstance(){
//把实例的创建时机推迟到 第一次使用的时候
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
private SingletonLazy(){}
}
多线程懒汉模式

饿汉模式不涉及到修改操作,return instance 是读操作,多线程去读同一个变量,没有线程安全问题

java 的赋值虽然是原子的,前提是符合 instance == null,只有符合条件才能赋值,所以需要把条件判断和赋值操作看作一个整体。在多线程的情况下,如果有 2 个线程同时 getInstance 获取实例,那就有可能在 线程1 条件判断后,操作系统去调度线程2,线程2 创建 实例1 完成后,再次调度回线程1,那么 线程1 就会重复创建实例 ------实例2,虽然 线程2 创建的实例2由于没有被引用,被回收了, 但这仍然是线程安全问题
懒汉模式的线程不安全只是在创建实例之前,创建好实例之后都是读操作,不会存在线程安全问题
解决方式就是把这 2 个操作打包成一个整体,也就是加锁 :
java
public static SingletonLazy getInstance(){
synchronized (locker){
if(instance == null){
instance = new SingletonLazy();
}
}
return instance;
}
这样有一个线程加锁,即使是去调度其他线程其他线程会进入堵塞状态,实例就不会被多次创建。这样线程安全问题就解决了。但是每次调用 getInstance() 都会加锁,不管这个实例是不是创建了。懒汉模式只是在实例创建前存在线程安全问题,这样不必要的加锁,就会导致其他线程堵塞,影响效率
解决方式是在加锁前进行判断是否要加锁 :
java
public static SingletonLazy getInstance(){
//判断是否要加锁
if(instance == null){
synchronized (locker){
if(instance == null){
instance = new SingletonLazy();
}
}
}
return instance;
}
如果实例已经创建好了,那就不需要加锁,如果没创建好实例,那就需要加锁保证线程安全,不必要的加锁就不存在了。
但是 instance = new SingletonLazy();可能存在指令重排序的情况的,在创建实例的时候可以分成 3 个步骤:
- 分配内存空间
- 针对空间进行初始化
- 把内存空间的地址赋值给引用变量

但是由于指令重排序,1 => 2 => 3 可能会变成 1 => 3 => 2 很可能后面的代码会使用未初始化的成员属性,对代码逻辑产生影响。那么应该使用 volatile 关键字,禁止指令重排序
java
private volatile static SingletonLazy instance = null;