目录
3.3双重监察锁DCL(double check locker):
1,什么是单例模式:
单例模式是指在程序中只存在一个对象实例,在程序的任意时间获取这个类的对象,返回的都是同一个对象,java中每一个类都有唯一的一个对象,类对象
2,单例模式的作用:
单例模式可以保证系统中一个类只有一个实例而且该实例又易于外界访问,从而方便对实例个数的控制并节约系统资源
3,如何实现单例模式
单例模式不对外提供对象的构造方法,而是在类内部使用构造方法完成对单例对象的创建,防止被其他线程错误创建
3,1 懒汉式加载:
单例对象为instance,不提供对外的构造方法,其他程序可以使用SingletonLazy.getInstance()方法完成对单例对象instance对象的调用,在调用getinstance()方法时,如果instance还未创建,那么就会创建一个instance,如果instance已经创建,便返回创建好的instance
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy(){}
//对外提供一个获取单例对象的方法
public static SingletonLazy getInstance(){
//判断需要返回的对象是否为空
if (instance == null) {
//为空创建对象
instance = new SingletonLazy();
}
//返回对象
return instance;
}
}
这样构造单例对象在单线程的情况下是没有什么问题的,但是在多线程的情况下就会出现问题,
//演示懒汉式加载线程不安全现象
public static void main(String[] args) {
//多个线程并发获取单例对象
for(int i = 0 ;i<10;i++){
Thread thread = new Thread(()->{
SingletonLazy instance = SingletonLazy.getInstance();
System.out.println(instance);
});
thread.start();
}
}
可以看出在用十个线程进行调用SingletonLazy.getInstance()方法时产生了错误,出现了好个不同的对象,这和我们的预期是相悖的,为什么会出现这样的问题呢?
这是因为在刚开始的时候,instance还未创建,A线程读取instance的值为null,判断过后准备创建instance,但是此时的instance对象还未创建,也未写入内存,因此B线程此时读取的instance的值也是null,也会进行创建instance,并对A线程创建的instance进行覆盖,因此就会出现这种现象
优缺点:
优点:浪费少,效率高,实现和程序启动时的错峰加载
缺点:多线程情况下会出现线程不安全的现象
3.2 饿汉式加载
单例对象为instance,不提供对外的构造方法,其他程序可以使用SingletonLazy.getInstance()方法完成对单例对象instance对象的调用,但是与SingletonLazy不同的是instance从一开始就被创建完成,所以不论是单线程还是多线程情况,instance都是唯一的(类加载的时候就完成了对对象的创建,使用很迫切,不想等)
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
//无参构造方法私有化
private SingletonHungry(){}
public static SingletonHungry getInstance(){
//对外提供一个获取类成员变量的方法
return instance;
}
}
public static void main(String[] args) {
//多个线程并发获取单例对象
for(int i = 0 ;i<10;i++){
Thread thread = new Thread(()->{
SingletonHungry instance = SingletonHungry.getInstance();
System.out.println(instance);
});
thread.start();
}
}
优缺点:
优点:不会出现线程安全问题
缺点:程序启动时需要加载很多类,但是我们当前的单例类,并不是一启动就要用,因此会浪费一定的计算机资源
3.3双重监察锁DCL(double check locker):
单例对象为instance,不提供对外的构造方法,其他程序可以使用SingletonLazy.getInstance()方法完成对单例对象instance对象的调用,getinstance()方法会对instance是否null的判断加锁,保证了单例对象的唯一性,而由于线程不安全问题只会在instance创建时出现,当instance创建完成后还继续申请锁资源进入判断就是浪费计算机资源,这是完全没有必要的,因此在锁之外再次进行了instance是否null的判断,如果是,则去竞争锁资源,如果不是就跳出,避免了从用户态到内核态的资源消耗,这种两个if中间进行锁竞争的情况就被称为双重监察锁(DCL)
public class SingletonDCL {
//volatile解决指令重排序的问题
private static volatile SingletonDCL instance = null;
private SingletonDCL(){}
public static SingletonDCL getInstance(){
//加锁是为了防止创建的单例对象被其他线程刷新,
//但由于问题只会发生在刚开始instance为null的时候,
//所以当instance不为空后还继续进行锁竞争的话,
//会造成较大的资源浪费,所以需要在锁之外再进行判断
if(instance==null){
//进行加锁
synchronized (SingletonDCL.class) {
//判断需要返回的对象是否为空
if (instance == null) {
//为空创建对象
instance = new SingletonDCL();
}
}
}
//返回对象
return instance;
}
}
DCL解决了懒汉式加载的线程安全问题和饿汉式加载的重复加锁的问题