单例模式

1. 定义

单例模式:确保一个类只有一个实例,并提供一个全局访问点。

  • 按照定义来看,也可以设置一个全局变量,同样能实现要求,但是全局变量却存在问题,如果将对象赋值给一个全局变量,那么必须在程序一开始就创建好对象,万一这个对象非常耗费资源,而程序在这次的执行过程中又一直没有用到它,就形成浪费了。

2. 用处

有一些对象只需要一个,例如配置文件,工具类,线程池,缓存,日志对象等等。单例模式保证应用中有且只有一个实例。

常用的单例模式

(1)懒汉式:指全局的单例实例在第一次被使用时构建。
(2)饿汉式:指全局的单例实例在类装载时构建。

日常我们使用的较多的应该是懒汉式的单例,毕竟按需加载才能做到资源的最大化利用。

3. 实现

原理:利用静态类变量、静态方法和适当的访问修饰符

3.1. 经典的单例模式实现

/**
 * 经典的单件模式实现
 */
public class Singleton01 {

    // 利用一个静态变量来记录Singleton类的唯一实例
    private static Singleton01 uniqueInstance;

    /*
        别的成员变量
     */

    // 把构造器声明为私有的,只有从Singleton类内部才可以调用构造器
    private Singleton01() {
    }

    //
    public static Singleton01 getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton01();
        }
        return uniqueInstance;
    }

    /*
        别的方法实现
     */
}

上面是单例模式最经典也是最简单的实现方法,但是当程序使用多线程的时候就会出现问题,如下图所示:



在上图中的程序就会出现有多个对象的情况,这是因为使用了多线程,处理该多线程灾难的一个方法是把getInstance()变成同步(synchronized)方法。

3.2. 处理多线程

经典单例遇到多线程就会创造出多于一个的实例,只要把getInstance()变成同步(synchronized)方法,多线程灾难就可以轻易地解决了。

/**
 * 处理多线程:
 *      经典单例遇到多线程就会创造出多于一个的实例
 *      只要把getInstance()变成同步(synchronized)方法
 *      多线程灾难就可以轻易地解决了
 */
public class Singleton02 {

    // 利用一个静态变量来记录Singleton类的唯一实例
    private static Singleton02 uniqueInstance;

    /*
        别的成员变量
     */

    // 把构造器声明为私有的,只有从Singleton类内部才可以调用构造器
    private Singleton02() {
    }

    //
    public static synchronized Singleton02 getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton02();
        }
        return uniqueInstance;
    }

    /*
        别的方法实现
     */
}

但是这样问题又来了,为了解决多线程的问题,我们引入了同步(synchronized)方法,但是同步会降低性能,这又是另一个问题。

3.3. 改善多线程

经典单例遇到多线程会创造出多于一个的实例,但是使用同步(synchronized)方法又会降低性能,因此可以进一步改善多线程。

(1)如果getInstance()的性能对应用程序不是很关键,就什么都别做

(2)使用“饿汉式”创建实例,而不延迟实例化

这里就用到我们的“饿汉式”了,即,全局的单例实例在类装载时构建。
如果应用程序总是创建并使用单例对象的实例,或者在创建和运行时方面的负担不太繁重,就可以使用“饿汉式”创建实例。

package npu.yyl.pattern.singleton;

/**
 * 使用同步(synchronized)方法会降低性能
 * 如果应用程序总是创建并使用单例对象的实例,
 * 或者在创建和运行时方面的负担不太繁重,
 * 就可以使用“饿汉式”创建实例
 */
public class Singleton03 {

    // 在静态初始化器(static initializer)中创建单例
    // 这段代码保证了线程安全(thread safe)
    private static Singleton03 uniqueInstance = new Singleton03();

    private Singleton03() {
    }

    //
    public static Singleton03 getInstance() {
        // 已经有实例了,直接使用它
        return uniqueInstance;
    }

    /*
        别的方法实现
     */
}

利用这个做法,我们依赖JVM在加载这个类时马上创建此唯一的单例实例。JVM保证在任何线程访问uniqueInstance静态变量之前,一定先创建此实例。

(3)用“双重加锁”,在getInstance()中减少使用同步

利用双重检查加锁(double-checked locking),首先检查是否实例已经创建了,如果尚未创建,“才”进行同步。这样一来,只有第一次会同步,这正是我们想要的。

package npu.yyl.pattern.singleton;

/**
 * 利用双重检查加锁(double-checked locking),
 * 首先检查是否实例已经创建了,
 * 如果尚未创建,“才”进行同步。
 * 这样一来,只有第一次会同步。
 */
public class Singleton04 {

    // 利用一个静态变量来记录Singleton类的唯一实例
    private volatile static Singleton04 uniqueInstance;

    private Singleton04() {
    }

    public static synchronized Singleton04 getInstance() {

        if (uniqueInstance == null) {
            // 检查实例,如果不存在,就进入同步区块
            synchronized (Singleton04.class) {
                // 只有第一次才彻底执行这里的代码
                if (uniqueInstance == null) {
                    // 进入区块后,再检查一次。如果仍是null,才创建实例
                    uniqueInstance = new Singleton04();
                }
            }
        }
        return uniqueInstance;
    }

    /*
        别的方法实现
     */
}

volatile关键词确保,当uniqueInstance变量被初始化成Singleton实例时,多个线程正确的处理uniqueInstance变量。
这个方法关心了性能,大大减少了getInstance()的时间消耗。

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

推荐阅读更多精彩内容