一、使用场景
单例模式只允许创建一个对象,因此节省内存,加快对象访问速度。
1.需要频繁实例化然后销毁的对象。
2.创建对象时耗时长,耗资源,又经常用到的对象。
3.频繁访问数据库或文件的对象
二、懒汉式
在类加载的时候不被初始化
不加synchronized线程不安全,加synchronized保证线程安全,但是效率低,多数情况下不需要同步。
private static SingletonUtil instance;
//线程不安全
public static SingletonUtil getInstance() {
if (instance == null) {
instance = new SingletonUtil();
}
return instance;
}
//线程安全
public static synchronized SingletonUtil getInstance2() {
if (instance == null) {
instance = new SingletonUtil();
}
return instance;
}
三、饿汉式
在类加载时已经完成初始化,线程安全
不管用不用,都已经初始化了,有可能这个类一次都用不到
private static SingletonUtil instance = new SingletonUtil();
public static SingletonUtil getInstance() {
return instance;
}
饿汉式与懒汉式
懒汉式:在类加载时不被初始化,需要使用synchronized才保证线程安全,加载快,获取对象慢。
饿汉式:在类加载就完成了初始化,不用synchronized,线程安全,加载慢,获取快
四、静态内部类
延迟加载,线程安全
缺点:无法传参
public class SingletonUtil {
private static class SingletonHolder {
private static final SingletonUtil instance = new SingletonUtil();
}
public static final SingletonUtil getInstance() {
return SingletonHolder.instance;
}
}
五、双重校验锁
第一次校验:也就是第一个if,这个时为了代码提高执行效率,由于单例模式只要一次创建实例既可,所以当创建一个实例之后就不用synchronized进入同步代码块了,直接返回就行了。
第二次校验:也就是第二个if,这个校验是防止二次创建实例,例如有一种情况,当singleton还没有创建时,线程T1调用getInstance方法,由于第一次判断signleton==null,此时线程T1准备继续执行,但是资源被线程T2抢占,此时T2也调用了getInstance()方法,同样由于singleton没有实例化,T2同样可以通过第一个if,然后继续往下执行同步代码块,第二个if也通过了,然后T2创建了一个实例singleton。此时T2完成任务,资源回到T1线程,T1也进入同步代码块,如果没有第二个if,那么T1也会创建一个singleton实例,那么就会出现创建多个实例的情况,但是加上第二个if,就可以完全避免这个多线程导致多次创建实例的问题。
public class SingletonUtil {
private volatile static SingletonUtil singletonUtil;
public static SingletonUtil getInstance5() {
if (singletonUtil == null) {
synchronized (SingletonUtil.class) {
if (singletonUtil == null) {
singletonUtil = new SingletonUtil();
}
}
}
return singletonUtil;
}
}
5.1为什么要使用volatile
singletonUtil = new SingletonUtil()不是原子操作,jvm做了三件事:
1.给singletonUtil分配内存
2.调用SingletonUtil的构造函数来初始化,创建对象
3.将singletonUtil对象指向分配的内存空间(执行完这步 singleton才是非 null了)
由于指令重排,可能是1-2-3也可能是1-3-2,如果是1-3-2,那么执行完3之后,2之前,线程2来执行这里,在第一个null判断时,就直接返回,但此时singletonUtil只有内存地址,没有执行2创建对象,那么后续使用就报错。