设计模式篇|单例模式

一、简介

1.什么是设计模式

设计模式是一套被 反复使用、多数人知晓、经过分类编目的、代码设计经验的总结。

2.为什么要使用设计模式

为了可重用代码,让代码更容易的被他人理解并保证代码的可靠性。

二、GOF23

创建型模式:

单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式

结构性模式:

适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式

行为型模式

模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式、访问者模式

三、单例模式概念及其实现

核心作用:
  • 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点

常见应用场景
1.Windows的Task Manager(任务管理器)就是很经典的单例模式
2.Windows的Recycle Bin(回收站)也是典型的单例模式应用,在整个系统运行中,回收站一直维护者仅有的一个实例
3.网站的计数器,一般也采用单例模式,否则难以同步
4.数据库的连接池设计一般也采用单例模式,因为数据库连接是一种数据库资源
5.在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理

单例模式的优点

1.由于单例模式只生成一个实例,减少了系统性能开销。
2.单例模式可以在系统设置全局访问点,优化环共享资源访问。

单例模式的五种实现方式
主要
  • 饿汉式(线程安全,调用效率高。但是不能延时加载)
  • 懒汉式(线程安全,调用效率不高。但是可以延时加载)
其他
  • 双重检测锁式(由于JVM底层内部模型原因,偶尔会出现问题,不建议使用)

  • 静态内部类式(线程安全,调用效率高,可以延时加载)

  • 枚举单例(线程安全,调用效率高,不能延时加载)

  • 饿汉式实现
/**
 * 饿汉式单例模式
 */
public class SingletonDemo01 {

    //1.将实例设置为静态、私有的
    //类初始化时立即加载
    private static SingletonDemo01 instance = new SingletonDemo01();

    //2.将构造器私有
    private SingletonDemo01(){
    }

    //3.提供访问点
    public static SingletonDemo01 getInstance(){
        return instance;
    }
}

在公有方法处不需要添加synchronized,因为静态对象在类加载时进行初始化,此时天然线程安全,但是没有延时加载,如果最终资源没有被使用,会造成资源的浪费。因为没有加同步,所以执行效率高。

  • 懒汉式实现
/**
 * 懒汉式单例模式
 */
public class SingletonDemo02 {

    private static SingletonDemo02 instance;

    private SingletonDemo02(){

    }

    public static synchronized SingletonDemo02 getInstance(){
        if (instance == null){
            instance = new SingletonDemo02();
        }
        return instance;
    }
}

懒汉式在公有方法处需要加synchronized关键字,因为不添加同步的话,在多线程情况下有可能会对单例模式造成破坏。懒汉式加载可以避免资源的浪费,但是因为加了同步,所以调用效率比较低。

  • 双重检测锁实现
/**
 * 双重检测锁式
 */
public class SingletonDemo03 {

    private static SingletonDemo03 instance = null;

    private SingletonDemo03(){}

    public static SingletonDemo03 getInstance(){
        if (instance == null){
            SingletonDemo03 sc;
            synchronized (SingletonDemo03.class){
                sc = instance;
                if (sc == null){
                    synchronized (SingletonDemo03.class){
                        if (sc == null){
                            sc = new SingletonDemo03();
                        }
                    }
                    instance = sc;
                }
            }
        }
        return instance;
    }
}

优点:

  • 调用效率高、线程安全、实现了延迟加载

缺点:

  • 由于JVM模型的原因,有时候会出现错误,所以不建议使用
  • 静态内部类式
public class SingletonDemo04 {

    private SingletonDemo04(){}

    private static class SingletonClassInstance{
        private static final SingletonDemo04 instance = new SingletonDemo04();
    }

    public static SingletonDemo04 getInstance(){
        return SingletonClassInstance.instance;
    }
}

要求:

  • 外部没有static属性,则不会像饿汉式那样立即加载对象。

  • 只有真正调用getInstance()方法,才会加载静态内部类,加载类时是线程安全的。instancestatic final类型,保证内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程的安全。

  • 兼备了线程安全、调用效率高、延迟加载的优势。

  • 枚举方式
public enum  SingletonDemo05 {

    /**
     * 定义一个枚举元素,他就代表了Singleton的一个实例
     */
    INSTANCE;
}

优点:

  • 实现简单
  • 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞。

缺点:

  • 无延迟加载
如何选用
  • 单例对象、占用资源少、不需要延迟加载时
    使用枚举式好于饿汉式
  • 单例对象、占用资源大、需要延迟加载时
    静态内部类式好于懒汉式
关于单例模式的破解

注意:枚举方式由于JVM底层的原因,所以无法破解
方式一:通过反射

        //通过反射方式直接调用私有构造器
        Class<SingletonDemo01> clazz = (Class<SingletonDemo01>) Class.forName("com.hxx.singleton.SingletonDemo01");
        //获取构造器
        Constructor<SingletonDemo01> constructor = clazz.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        //通过构造器创建实例
        SingletonDemo01 s3 = constructor.newInstance();
        SingletonDemo01 s4 = constructor.newInstance();

        System.out.println(s3 == s4);

方式二:通过反序列化

        //通过反序列化方式调用
        FileOutputStream fos = new FileOutputStream("D:/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s1);

        oos.close();
        fos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/a.txt"));
        SingletonDemo01 s3 = (SingletonDemo01) ois.readObject();
        System.out.println(s3);
解决破解

破解反射

    //2.将构造器私有
    private SingletonDemo01(){
        if (instance != null){
            throw new RuntimeException();
        }
    }

在构造器中对instance进行判断

破解反序列化

`  private Object readResolve(){
        return instance;
    }

在单例模式的方法中添加readResolve方法。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容