单例模式
单例模式属于一种设计模式,它指当前类只允许有一个实例对象。缘由是可以想象当一个类非常大,实例化一个对象需要大量的内存空间,实例化多个对象的话内存是无法接受的。
单例模式有细分为饿汉模式和懒汉模式。
饿汉模式
📢饿汉模式是指当前类的唯一实例已经准备好了,就等着调用了。
class Hungry {
private static Hungry hungry = new Hungry();
public static Hungry getHungry() {
return hungry;
}
// 禁止外部创建实例
private Hungry() {
}
}
-
线程安全问题:
饿汉模式在多线程模式下是安全的。因为获取实例时,直接返回实例,没有读或者写操作,不涉及原子性等。
懒汉模式
📢懒汉模式是指需要实例则再去创建。
class Lazy {
private static Lazy lazy;
public static Lazy getLazy() {
if (lazy == null) {
lazy = new Lazy();
}
return lazy;
}
// 禁止外部创建实例
private Lazy() {
}
}
-
线程安全问题
懒汉模式在多线程模式下是不安全的。
关键点集中在创建实例时要判断是否已经创建,这涉及到读操作,如果没有创建,则实例化对象,这涉及到写操作。
安全问题:
📌读写操作能否保证原子性?实例化会不会被指令重排序?
原子性问题
:如果多个线程并发进入load,则会创建多个实例。
指令重排序问题
:“lazy = new Lazy();”,
实例化对象的过程主要涉及这几个步骤:
1.申请内存空间-->2.实例化对象,同时初始化内容-->3.引用赋值。
编译器可能会对步骤2和3进行优化,颠倒顺序,即进行指令重排序。
指令重拍序的问题在于
,如果是单线程,那么可以保证引用初次访问对象前进行步骤3初始化。但是在多线程的情况下,如果在步骤2完成后,那么会从synchronized中出去,其他线程则会进来,当这个线程判断不为空后,它得到这个为初始化的对象就会使用了,那么只要这个使用在步骤2完成前,那么一定会出现问题。
📈✔优化:
class Lazy {
private volatile static Lazy lazy; // 优化一
private static final Object locker = new Object();
public static Lazy getLazy() {
if (lazy == null) {
synchronized (locker) { // 优化二
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
// 禁止外部创建实例
private Lazy() {
}
}
📌注解:
synchronized和volatile的作用
- synchronized使得并发线程进行竞争,保证线程安全,同时保证原子性。
- volatile保证不会被指令重排序。
为什么要双层if?
-
外部的if
是为了减少锁竞争,如果对象不空则不用等待锁。如果外部if去掉,多个线程直接竞争锁,不管你是否已经实例化了,都得竞争一下子,这样效率比较低!!! -
内部的if
是为了判断是否实例化对象,主要针对哪些在未实例化之前并发进入竞争锁的哪些线程。如果内部if去掉,多个线程进入竞争锁后,那么一定会多次创建对象!!!
-