目录
一、单例模式
单例模式是指一个类在进程中只有唯一的一个实例。
单例模式可以被分为饿汉和懒汉两个模式,二者相比饿汉模式是急迫的,而懒汉模式则是从容的。
例:
打开一个硬盘上的文件,读取文件的内容并显示出来。
- 饿汉模式就是把文件的所有内容都读取到内存中并显示出来。
- 懒汉模式则是只读取文件的一小部分来填充给当前屏幕页面,如果用户翻页,再读取其他文件内容。
二者相比,前者花费时间更长且可能会内存不够,而后者可以快速打开。但是二者线程安全却是相反的,饿汉模式是天然的线程安全,它只是读数据不修改数据;懒汉模式则既读数据又修改数据,是不安全的,多线程下可能无法保证创建对象的唯一性。
1、饿汉模式
饿汉模式就是在类加载时创建对象,这种方式是线程安全的。
class Singleton {
// 唯一实例的本体
private static Singleton instance = new Singleton();
// 获取到实例的方法
public static Singleton getInstance() {
return instance;
}
// 禁止外部 new 实例
private Singleton() { }
}
2、懒汉模式
(1)、懒汉模式
懒汉模式是在类加载时不创建对象,而是在第一次使用时才创建对象。
class SingletonLazy {
private static SingletonLazy instance = null;
public static getInstance() {.
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
private SingletonLazy() { }
}
懒汉模式的代码在单线程下没有任何问题,但是在多线程环境下则存在安全问题。
当对象还没有被创建时,如果有N个线程都调用getInstance()方法,就可能创建N个对象,因此存在线程安全问题。那要如何解决代码中的线程安全问题呢?下面便是改进方法:
(2)、通过synchronized加锁
class SingletonLazy {
private static SingletonLazy instance = null;
public static synchronized SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
private SingletonLazy() { }
}
我们可以通过加锁把if和new操作变为原子性来保证线程安全,但加锁可能涉及到阻塞等待,这是一个比较低效的操作。
虽然使用synchronized加锁能够保证线程安全,但是每次调用该方法时都会产生锁的竞争。然而创建实例只需要创建一次,在创建实例后再调用该方法还需要将锁释放,效率较低。因此我们需要再次改进。
(3)、通过双重校验锁判定和volatile关键字
class SingletonLazy {
volatile private static SingletonLazy instance = null;
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
private SingletonLazy() { }
}
1、使用双重if判定,可以减少不必要的加锁操作,降低了锁的竞争
- 外层的if判定:判定是否要加锁,如果对象已经有了, 就不必加锁了, 此时本身就是线程安全的。
- 内层的if判断:当前线程释放锁后,其他阻塞的线程就会继续竞争锁,但对象已经创建好了,所以为了避免再次产生锁的竞争降低效率,于是就有了内层判定。
2、使用volatile修饰instance,禁止指令重排序保证后续线程能拿到完整对象