文档章节

不管你年底换不换工作,了解下单例模式

liululee
 liululee
发布于 2019/12/01 23:01
字数 1191
阅读 23
收藏 0

1. 单例模式

什么是单例模式?简言之就是确保定义为单例模式的类在程序中有且只有一个实例。单例模式的特点:

  1. 只有一个实例 (只能有一个对象被创建)

  2. 自我实例化(类构造器私有)

  3. 对外提供获取实例的静态方法

2.单例模式的实现

常见的单例模式实现方式有五种:

2.1. 懒汉式

懒汉式(一般也称之为 饱汉式),具体代码实现如下:


public class Singleton {

    /**
     * 自我实例化
     */
    private static Singleton singleton;

    /**
     * 构造方法私有
     */
    private Singleton() {
        System.out.println("创建单例实例...");
    }

    /**
     * 对外提供获取实例的静态方法
     */
    public static Singleton getInstance() {
        if (null == singleton) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

从代码实现中可以看到,实例并不是在一开始就是初始化的,而是在调用 **getInstance()**方法后才会产生单例,这种模式延迟初始化实例,但它并非是线程安全的。

public class SingleTonTest {

    /**
     * 多线程模式下测试懒汉模式是否线程安全
     *
     * @param args
     */
    public static void main(String[] args) {
        /**
         * 这里我图方便,直接用Executors创建线程池
         * 阿里巴巴开发手册是不推荐这么做的
         */
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            executorService.execute(() -> System.out.println(Thread.currentThread().getName() + "::" + Singleton.getInstance()));
        }
    }
}

测试结果截图:

懒汉式

懒汉式是在运行时加载对象的,所以加载该单例类时会比较快,但是获取对象会比较慢。且这样做是线程不安全的,如果想要线程安全,可以在getInstance()方法加上synchronized 关键词修饰,但这样会让我们付出惨重的效率代价。

2.2. 饿汉式

提前创建好实例对象,调用效率高,但无法延时加载,容易产生垃圾,线程安全。

public class Singleton {

    /**
     * 自我实例化
     */
    private static Singleton singleton = new Singleton();

    /**
     * 构造方法私有
     */
    private Singleton() {
        System.out.println("创建单例实例...");
    }

    /**
     * 对外提供获取实例的静态方法
     */
    public static Singleton getInstance() {
        return singleton;
    }
}

2.3. 双重检查锁模式

public class Singleton {

    /**
     * 自我实例化,volatile修饰,保证线程间可见
     */
    private volatile static Singleton singleton;

    /**
     * 构造方法私有
     */
    private Singleton() {
        System.out.println("创建单例实例...");
    }

    /**
     * 对外提供获取实例的静态方法
     */
    public static Singleton getInstance() {
        // 第一次检查,避免不必要的实例
        if (singleton == null) {
            // 第二次检查,同步,避免产生多线程的问题
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

由于singleton=new Singleton()对象的创建在JVM中可能会进行重排序,在多线程访问下存在风险,使用volatile修饰signleton实例变量,能禁止指令重排,使得对象在多线程间可见,能够有效解决该问题。

双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因

2.4. 静态内部类模式

public class Singleton {
    /**
     * 构造方法私有
     */
    private Singleton() {
        System.out.println("创建单例实例...");
    }

    private static class SingletonInner {
        private static Singleton instance = new Singleton();
    }

    private static Singleton getInstance() {
        return SingletonInner.singleton;
    }
}

这样写充分利用静态内部类的特点——初始化操作和外部类是分开的,只有首次调用**getInstance()**方法时,虚拟机才加载内部类(SingletonInner.class)并初始化instance, 保证对象的唯一性。

2.5. 枚举单例模式

public enum Singleton {
    INSTANCE 
}

感觉异常简单,默认枚举类创建的对象都是单例的,且支持多线程。

3.单例模式总结

  1. 单例模式优点在于:全局只会生成单个实例,所以能够节省系统资源,减少性能开销。然而也正是因为只有单个实例,导致该单例类职责过重,违背了**“单一职责原则”**,单例类也没有抽象方法,会导致比较难以扩展。
  2. 以上所有单例模式中,推荐使用静态内部类的实现,非常直观,且保证线程安全。在《Effective Java》中推荐枚举类,但太简单了,导致代码的可读性比较差。
  3. 单例模式是创建型模式,反序列化时需要重写**readResovle()**方法,以保证实例唯一。

文章首发于本人博客:www.developlee.top, 转载请注明出处!

关注公众号,后台回复666, 领取福利:

liululee

© 著作权归作者所有

liululee
粉丝 129
博文 80
码字总数 117498
作品 0
杭州
程序员
私信 提问
在JavaScript中理解策略模式

设计模式是: 在面向对象软件过程中针对特定问题的简洁而优雅的解决方案. 通过对封装、继承、多态、组合等技术的反复利用, 提炼出可重复使用面向对象的设计技巧. JavaScript 可以模拟实现传统...

夜曉宸
2019/01/27
0
0
JavaScript设计模式总结

之前看过《JavaScript设计模式与开发实践》这本书,对书中的设计模式和一些相关案例也有了一定的了解,同时把这些设计模式的应用对应在在一些其他的项目中,进行了一些整理,如下仅供参考: ...

jefferyE
2019/03/26
0
0
设计模式梳理(一)

设计模式梳理(一) 总体来说设计模式分为三大类: @案例源码地址:https://gitlab.com/lxqxsyu/DisgnPattern 创建型模式 简单工厂模式 工厂类是整个模式的关键。它包含必要的判断逻辑,能够...

lxq_xsyu
2017/11/02
0
0
6-Java面向对象-单例模式

设计模式的官方解释: 一套被反复使用,经过分类编目,多数人知晓的,代码设计经验的总结。 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。 对于建一栋房子,只要类型确定...

天涯明月笙
2018/08/05
0
0
Javascript设计模式与开发实践详解(二:策略模式)

上一章我们介绍了单例模式及JavaScript惰性单例模式应用 这一次我主要介绍策略模式 策略模式是定义一系列的算法,把它们一个个封装起来,并且让他们可以互相替换。 比方说在现实中很多时候也...

littl_Prince
2016/04/06
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Lucene 技术分享

提纲: 问题引入: 为啥模糊查询搜索引擎比数据库快?快在哪、快的原因,快的代价 为什么mysql等不行,为什么ES、Solr就很快 B+树和FST数据结构比较一下,说明其快的原因 FST数据结构 详细介...

Java搬砖工程师
5分钟前
2
0
linux go 开发环境搭建

下载go安装包 wget https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz 解压 tar -C /usr/local/ -xvf go1.10.3.linux-amd64.tar.gz chmod 777 -R /usr/local/go/* 设置环境变量 vim /e......

jorin_zou
18分钟前
3
0
MySQL中使用备库作逻辑备份,如何处理主库的DDL语句

假设DDL针对表xt ## 确保可重复读隔离级别S1: set session transaction isolation level repeatable read;## 确保能得到一个一致性视图S2: start transaction with consistent snapshot;...

Jacktanger
25分钟前
2
0
Git高级之配置多个SSH key

最近我们在代码托管平台上使用SSH的方式下拉代码,通常是用一个ssh key来拉取所有托管平台的代码,如码云,GitHub、GitLab等,但是总用一个不是太好。会有安全风险,这就需要为每个托管平台设...

我们都很努力着
27分钟前
4
0
获取map()以在Python 3.x中返回列表

我正在尝试将列表映射为十六进制,然后在其他地方使用该列表。 在python 2.6中,这很简单: 答: Python 2.6: >>> map(chr, [66, 53, 0, 94])['B', '5', '\x00', '^'] 但是,在Python 3.......

技术盛宴
35分钟前
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部