单例模式
单例模式就是该类在整个程序的使用过程中有且仅有一个实例。也就是它的构造方法是私有的,该类负责创建自己的对象,同时确保只有一个对象被创建。当有些封装的类会去重复加载,使用单例模式可以节省资源。
单例模式有哪些?
- 懒汉模式
- 双重检查加锁
- 饿汉模式
- 静态内部类
(还有枚举,枚举的可读性差就先不看了)
懒汉模式
懒汉模式就是用到就通过调用getStance方法进行实例化。
public class Single {
private static Single instance = null;
/*
* 构造方法
*/
private Single() {
}
public static Single getStance() {
if (instance == null) {
instance = new Single();
}
return instance;
}
}
懒汉模式的情况会遇到一种问题,就是在多线程的情况下,在执行到if (instance == null)
时时间片切换了,时间片切换到下一个线程也执行if (instance == null)
由于上一个线程还没有执行完实例化对象,instance是空的所以它还会new一个对象。这样就会有多个对象,单例模式就会失效了。解决办法是加上同步锁synchronized
:
public static synchronized Single getStance() {
加上同步锁之后,getStance方法就需要等一个线程执行完之后其他线程才能调用这个方法。但是又出现了新的问题
同步锁会消耗很多资源,在每次调用getStance方法时,都需要执行同步锁,弊端显而易见。
饿汉模式
饿汉模式就是,这个类在创建的时候我就把我需要的东西准备好。就一开始就new出对象。
public class Single {
private static Single instance = new Single();
/*
* 构造方法
*/
private Single() {
}
public static Single getStance() {
if (instance == null) {
instance = new Single();
}
return instance;
}
}
那么从一开始就new出对象,不管几个线程,不管哪个线程执行到哪一步的时候时间片变了,里面都已经有了这个对象了。这样也会有个问题
,无论什么情况都要创建,浪费了内存资源。
双重检查加锁
为了避免懒汉模式和饿汉模式所带来的问题,可以使用双重检查加锁。
public class Single {
private volatile static Single instance = null;
/*
* 构造方法
*/
private Single() {
}
public static Single getStance() {
if (instance == null) { //第一个instance == null
synchronized (Single.class) {
if(instance == null) { //第二个instance == null
instance = new Single();
}
}
}
return instance;
}
}
第二次校验(instance == null),这个校验是为了防止二次创建实例。在多线程的情况下,先instance还未被实例化时,线程A进入了第一次校验,并且顺利进入到第二次校验的位置。此时资源被线程B抢占了。由于线程A还没有来得及走到new Single(),未被实例化。所以线程B可以顺利进入到new Single(),进行实例化。线程B走完了,线程A重新拿到资源往下走,第二次校验 instance == null时,instance已经被线程B实例化了,所以避免了二次创建实例。
对于双重检查加锁需要在private volatile static Single instance = null;
中加入了volatile
。这个关键词作用是防止指令重排
。需要了解学习volatile。
静态内部类
在饿汉模式的时候提到,在创建类的时候就实例化对象,该方法会造成即便我不用也会创建,使得内存资源的浪费。这咋办嘛?那就在第一次调用的时候在创建呗,并且加上关健词final。保证内存空间地址值不会改变。只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,
方便理解:new出来的对象存放在堆当中的一个内存空间中,然后讲对象赋值给变量,变量是在栈当中生成的,那么在内存空间中使得new Single()和instance相关联的肯定不“=”,而是十六进制的地址值,对堆中的内存空间地址值赋值给变量(可以直接打印变量来查看地址值)。那么通过加上final是的第一次赋值后变量就无法改变了,地址值没法改变,所以内存空间就没法改变,保证了对象的唯一性,从而实现单例模式。
目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。
public class Single {
private Single() {
}
public static Single getStance() {
return Inner.instance;
}
private static class Inner {
private static final Single instance = new Single();
}
}
单例模式会被反序列化进行破坏,通过加入readResolve方法让实例唯一
private Object readResolve() throws ObjectStreamException{
return singleton;
}
单例模式还会被反射破坏
在构造方法中加入SecurityManager(安全管理器)来解决该问题。
好记性不如烂笔头