设计模式-单例模式

主目录:https://blog.csdn.net/qq_52681418/article/details/114828850

设计模式-单例模式

单例模式实际上就是类自己为自己创建对象,即内存里该类每次只会存在一个对象。

根据实例化的时机可分为:

  • 懒汉式
  • 饿汉式:代表有Runtime

此模式违背单一职责原则,因为它无法继承和实现,只关心自己内部

1.饿汉式

饿汉式实现简单,

  • 通过私有静态成员变量实例化自身:创建自身对象
  • 私有化构造器:禁止外部进行实例化
  • 静态方法:外部获取实力的入口。
public class Single1 {
	//处l直接赋值,还可以在静态代码块赋值
    private static Single1 single1=new Single1();

    //私有化构造器,使外部无法new对象
    private Single1(){}
    
    //外部获取对象的方法
    public static Single1 getSingle1(){
        return single1;
    }
    
}

这种类型简单粗暴,但存在问题:不管用不用该对象都会被创建,容易造成资源浪费。

2.懒汉式

使用懒汉式,对象不会在类加载时创建,而是在外部调用时创建

public class Single2 {

    private static Single2 single2;
    
    private Single2(){}

    public Single2 getSingle2(){
        if(single2==null){ //如果对象不存在,则创建,否则直接返回已有对象
            single2=new Single2();
        }
        return single2;
    }
}

可以发现1、2两种方式直接只是成员变量赋值的时机不同。

严格意义上讲,2不是单例模式。在高并发场景下假设多个线程同时访问getSingle2(),在new执行之前,if条件一定成立,此时就可能会出现多个线程通过if判断的情况,故而会出现多个实例。

3.同步锁机制

在2的基础上加工synchroized同步锁,线程进入该方法后锁住,阻止其它线程进入。

public class Single2 {

    private static Single2 single2;
    private Single2(){}

    public synchronized Single2 getSingle2(){

        if(single2==null){ //如果对象不存在,则创建,否则直接返回已有对象
            single2=new Single2();
        }
        return single2;
    }
}

某个线程进入方法后,立刻锁定该方法,其他线程无法进入,直到锁被释放,这样有效防止if被突破。

但它仍有问题:在创建时很安全,但对象创建之后线程全部都是取操作,加锁后只能逐个去取,影响性能。

4.双重检查锁

第一次创建时,会加锁,对象创建完毕后,由于外层条件不成立,因此不会进入同步块。


public class Single2 {

    private volatile static Single2 single2;
    private Single2(){}

    public  Single2 getSingle2(){
        if(single2==null) {
            synchronized (Single2.class) {
                if (single2 == null) { 
                    single2 = new Single2();
                }
            }
        }
        return single2;
    }
}

可以发现成员变量多出了 volatile 关键字,它的作用时不让JVM对指令优化和重排,防止多线程情况下出现空指针异常。

5.静态内部类

JVM在加载外部类时不会加载静态内部类。静态内部类实现简单,只是讲实例化操作转移到静态内部类中。

public class Single2 {
    
    private Single2(){}
    
    //静态内部类
    private static class Inner{
        private static final Single2 single2=new Single2();
    }

    public  Single2 getSingle2(){
        return Inner.single2;
    }
}

6.枚举

enum Single3{
    INSTANCE;
}

7.单例防止被破坏

怎么破坏?

  • 序列化操作
  • java反射操作

1.防止序列化破坏单例

在类中添加如下方法即可。

//此方法会在序列化时自动调用
   public Object readResolve(){
       return Single2.single2;
   }

2.防反射破坏单例

反射可以修改构造器的作用域,因此可以破坏单例,我们需要对构造方法进行改造。

    //private Single2(){}
    
    private static boolean is=false;
    private Single2(){
        synchronized (Single2.class){
            if (is){//如果被访问过了,抛出一个异常
                throw new IllegalArgumentException();
            }
            is=true;
        }
    }

实例化时会调用构造方法,在类中添加一个状态,记录构造器是否被使用过,如果使用过就抛异常。

3.改进后的双重检查锁


public class Single2 {

    private volatile static Single2 single2;

	//----------------------------------------------------用于构造
    private static boolean is=false;
    private Single2(){
        synchronized (Single2.class){
            if (is){//如果被访问过了,抛出一个异常
                throw new IllegalArgumentException();
            }
            is=true;
        }
    }
	//----------------------------------------------------用于获取实例
    public  Single2 getSingle2(){
        if(single2==null) {
            synchronized (Single2.class) {
                if (single2 == null) { 
                    single2 = new Single2();
                }
            }
        }
        return single2;
    }
    //----------------------------------------------------防序列化破坏
    public Object readResolve(){
        return Single2.single2;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值