什么是单例模式
单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
单例模式有两种类型:
懒汉式:在真正需要使用对象时才去创建该单例类对象
饿汉式:在类加载时已经创建好该单例对象,等待被程序使用
1、懒汉式,线程不安全
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。不要求线程安全,在多线程不能正常工作。(多线程的话同时访问可能会创建多个)。如果要多线程的话在方法上加上synchronized(同步锁)但是这会导致效率低下,并且很多时候都不用同步。
字面意义上只有去访问的时候才会创建对象.
public class Singleton {
private static Singleton singleton;
private Singleton (){}
/**
* 加上synchronized就是线程安全了,但是效率低使用双锁可以加快效率
*/
public static Singleton getInstance() {
//当对象等于null的时候创建对象
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
2、饿汉模式
这种方式比较常用,但容易产生垃圾对象,他在类加载的时候就会初始化创建对象。
public class Singleton{
/**
*这里直接创建好了对象掉用后就直接返回了
**/
private static Singleton singleton = new Singleton();
private Singletion(){}
public stitic Singleton(){
return singleton;
}
}
3、双检锁/双重校验锁(DCL,即 double-checked locking)
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
public class Singleton {
/**
*对比上面2个变量加上了 volatile修饰
**/
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
3.1、双锁为什么会效率比较高?
他规避了每次都要去获取锁,进入方法多个线程争抢同一个锁,第一个获取到锁的线程会再次判断singleton是否为空,因为singleton有可能已经被之前的线程实例化。实例化后在进入方法如果不为空就直接返回不会经过锁了
3.2、为什么用volatile
他有2个功能
(1)禁止指令重排序优化,
(2)保证内存可见性
首先创建对象会经过3步
(1)为singleton分配内存空间
(2)初始化singleton对象
(3)将singleton指向分配好的内存空间
但是JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能,就是指令重排序优化
(就是可能会先1(分配空间)然后3(直接指向分配的空间),最后2(初始化))
1、在变量上用volatile修饰,他会禁止指令重排序优化,在赋值后他会多一个操作增加一个内存屏障,他会令重排序时不能把后面的指令重排序到内存屏障之前的位置
2、volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。从而保证取到的都是最新的值,但普通变量做不到这点,(普通变量读取、赋值等都必须在工作内存中进行)。
3、volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
他是轻量级的同步机制