Design Pattern [1] —— 单例模式 Singleton

单例模式:一个类只能构造一个实例对象("构造器私有")

场景:

  Windows任务管理器、回收站

  项目中,配置文件的类,一般只有一个对象

  网站的计数器、时钟

  数据库连接池

  Servlet

  Spring中的Bean(缓存中取bean很快,减少jvm垃圾回收)(当有请求来的时候会先从缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象)

好文:https://zhuanlan.zhihu.com/p/33102022  (本文还需要进一步根据面经深化)

 


 我的Github里有源码,可以clone下来自己跑下:https://github.com/Yang2199/Design-Pattern/tree/master/src

 

1)饿汉式单例

  ==》上来直接new对象,所有类实例化。坏处是:大量浪费不必要的资源(因为很多类   不需要实例化)

public class Hungry {
    private Hungry(){} //构造函数
    private final static Hungry HUNGRY = new Hungry();//直接实例化,new出对象
    public static Hungry getInstance(){
        return HUNGRY;
    }
}

 

2)懒汉式单例 

   ==》懒汉模式中单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。

    线程不安全:多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,

    因此需要加锁解决线程同步问题( Synchronized 同步锁来修饰 getInstance 方法)

public class Singleton {
    private Singleton() {}  //单例=》私有构造器
    private static Singleton instance = null;  //单例对象
    public static Singleton getInstance() {  //调用getInstance方法才会开始构造对象
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

但是这种方法不符合线程安全:

public class Singleton {
    private Singleton() {
        System.out.println(Thread.currentThread().getName()); //构造时候打印线程名
    }  //私有构造函数
    private static Singleton instance = null;  //单例对象
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public static void main(String[] args) {
        for(int i =0;i<10;i++){
            new Thread(  ()->{Singleton.getInstance();}  ).start();  //1.启动新线程 2.lambda表达式
        }
    }
}

可能会出现构造了很多个的情形

 

方法一:双重锁检测 DCL

  ==》首先将类加同步锁(syn),但是new语句不是原子操作,所以对了类的实例加volatile锁(可见性)

第一把锁:synchronized锁

public static Singleton getInstance() {
    if (instance == null) {     //外层判断
        synchronized (Singleton.class) {    //将Singleton类 加锁
            if (instance == null) {     //原有判断
                instance = new Singleton();
          // 上面这行 不是原子操作:
          // 1.分配内存空间 2.执行构造方法,初始化对象 3.把这个对象指向这个空间 } } }
return instance; }
//加上两层:一层if、一层锁
//这样就能保证单例在多线程下的唯一性
//双重检查模式:DCL (Double Check)

 

第二把锁:volatile锁

private volatile static Singleton instance = null;  //由于instance = new Singleton()的非原子性(new对象3步),所以需要volatile保证强制同步一致

volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值

 

 

但是,还是可以用反射来破坏DCL:

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Singleton instance = Singleton.getInstance();
        Constructor<Singleton> declaredConstructor =Singleton.class.getDeclaredConstructor(null);  //通过反射获取declaredConstructor这个构造器
        declaredConstructor.setAccessible(true);
        Singleton instance2 = declaredConstructor.newInstance();

        System.out.println(instance.hashCode() );
        System.out.println(instance2.hashCode() );
    }

 


实现单例模式,3种方法对比:

 


 方法二:用静态内部类

public class Holder {
    private Holder(){}

    public static Holder getInstance() {return InnerClass.HOLDER;}

    private static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

这个方法也能被反射破坏单例的唯一性

 

 

方法三:枚举

  ==》直接把类名前的class替换成enum就好了,因为枚举无法反射

 

枚举:(通过查看源码可知,枚举时,无法反射。)

public enum EnumSingleton { //这里是enum而不是class
    INSTANCE;
    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

测试:

class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingleton instance1 = EnumSingleton.INSTANCE.getInstance();
Constructor
<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); EnumSingleton instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }

会显示:

异常:枚举无法创造反射

符合预期。

 

posted @ 2021-03-31 14:57  青杨风2199  阅读(179)  评论(0编辑  收藏  举报