Java设计模式之——单例模式

题引:

总结自己学习Java常用设计模式后的理解,本篇为单例模式。

未知的事物往往令人不知所措,为了透彻理解单例模式,我们必须知道:

什么是Java中的单例模式?

百度百科给出了Java中单例模式概念的内涵,Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”

由定义我们可以很清晰的抽象出:

实现Java单例模式类有哪些通用设计规则?

(1)私有化类构造器。
(2)定义静态私有的类对象。
(3)提供公共静态的获取该私有类对象的方法。

了解了单例模式的概念,以及单例模式的通用设计规则,对于如何实现一个Java单例,相信我们已不再感觉束手无策。但在了解具体实现之前,我们还需要了解:

Java单例模式解决了什么问题?

Java的单例模式主要解决了多线程并发访问共享资源的线程安全问题。

Java单例模式主要应用场景有哪些?

1.共享资源的访问与操作场景,如Windows系统的资源管理器,Windows系统的回收站,显卡的驱动程序,系统的配置文件,工厂本身(类模板),应用程序的日志对象等。

2.控制资源访问与操作的场景,如数据库的连接池,Java的线程池等。

单例模式的命名

单例的命名通常包含 singleton(以 singleton 开头或结尾) 或能按名称实际意义区分出在应用中唯一。

了解了Java单例模式出现的缘由以及出现的场合,那么,Java的单例究竟是以怎样的方式出现在这些场合中呢?

Java中单例模式的常用实现方式有哪些?

本文主要介绍4种常用的Java单例实现方式:饿汉式、懒汉式、注册登记式、序列化与反序列化式。

饿汉式

饿汉式,顾名思义,就是指在JVM首次访问到该单例类时,就会把该单例类对象创建出来,并保存在内存中,不管后续是否会使用到这个单例。

/**
 * @author jiangsj
 * 饿汉式
 * 优点:
 *     (1) 没有加任何的锁,执行效率较高
 *     (2) 线程安全
 * 缺点:
 *    (1) 类加载的时候就进行了初始化,程序后续未必会使用到该实例,导致内存浪费
 *    (2) 反射可破坏单例
 */
public class HungrySingleton {

    /** 私有化类构造器 */
    private HungrySingleton() {}

    /** 定义静态私有类对象 */
    private static HungrySingleton instance = new HungrySingleton();

    /** 提供公共静态的获取该私有类对象的方法 */
    public static HungrySingleton getInstance() {
        return instance;
    }

}

饿汉式-测试类

懒汉式

懒汉式,相比于饿汉式,它是指JVM首次访问到该单例类时,并不会实例化该单例类对象,只有等到后续被外部调用时,才会实例化该单例类对象。

饿汉式的写法比较固定,懒汉式由于延时加载的特性,写法上有一些变化,一般来说,懒汉式存在4个变种。

懒汉式1——无锁懒汉式
/**
 * @author jiangsj
 * 懒汉式——无锁,线程不安全
 * 优点:
 *     由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
 *
 * 缺点:
 *     (1) 该种实现方式存在线程不安全问题
 *     (2) 反序列化,反射与克隆可破坏单例
 */
public class LazySingletonWithoutSync {

    /** 私有化类构造器 */
    private LazySingletonWithoutSync() {}

    /** 定义静态私有类对象 */
    private static LazySingletonWithoutSync instance;

    /** 提供公共静态的获取该私有类对象的方法 */
    public static LazySingletonWithoutSync getInstance() {
        if (instance == null) {
            instance = new LazySingletonWithoutSync();
        }

        return instance;
    }

}

无锁懒汉式-测试类

懒汉式2——Sync同步锁懒汉式
/**
 * @author jiangsj
 * 懒汉式
 * 优点:
 *     (1) 由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
 *     (2) 线程安全
 * 缺点:
 *     (1) 给获取实例的公共方法加上同步锁synchronized,性能受到影响
 *     (2) 反序列化,反射与克隆可破坏单例
 */
public class LazySingletonWithSync {

    /** 私有化类构造器 */
    private LazySingletonWithSync() {}

    /** 定义静态私有类对象 */
    private static LazySingletonWithSync instance;

    /** 提供公共静态的获取该私有类对象的方法 */
    public static synchronized LazySingletonWithSync getInstance() {
        if (instance == null) {
            instance = new LazySingletonWithSync();
        }

        return instance;
    }

}

Sync同步锁懒汉式-测试类

懒汉式3——双重锁检查
/**
 * @author jiangsj
 * 懒汉式——双重锁检查单例
 * 优点:
 *     (1) 由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
 *     (2) 线程安全
 * 缺点:
 *     (1) 如果不加volatile关键词防止指令重排,双重锁检查单例可能会出现不完整实例
 *         分析:instance = new LazySingletonWithDoubleCheck() 操作并非原子操作,它包含如下三个操作指令:
 *             1) 分配对象的内存空间 memory = allocate()
 *             2) 初始化对象 ctorInstance(memory)
 *             3) 设置instance指向刚分配的内存地址 instance = memory
 *         经过指令重排序后,执行顺序可能如下:
 *             1) 分配对象的内存空间 memory = allocate()
 *             2) 设置instance指向刚分配的内存地址 instance = memory
 *             3) 初始化对象 ctorInstance(memory)
 *         若有A线程执行完上述重排序后的第二步,尚未初始化对象,此时B线程来获取单例instance,会发现instance不为空,于是返回该值,但实际该instance尚未构建完成,为不完整实例。
 *     (2) 反序列化,反射与克隆可破坏单例
 */
public class LazySingletonWithDoubleCheck {

    /** 私有化类构造器 */
    private LazySingletonWithDoubleCheck() {}

    /** 定义静态私有类对象 */
    private static volatile LazySingletonWithDoubleCheck instance;

    /** 提供公共静态的获取该私有类对象的方法 */
    public static LazySingletonWithDoubleCheck getInstance() {
        if (instance == null) {
            synchronized (LazySingletonWithDoubleCheck.class) {
                if (instance == null) {
                    instance = new LazySingletonWithDoubleCheck();
                }
            }
        }

        return instance;
    }

}

双重锁检查懒汉式-测试类

懒汉式4——静态内部类
/**
 * @author jiangsj
 * 懒汉式——静态内部类单例
 * 优点:
 *     (1) 由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
 *     (2) 在外部类被调用的时候内部类才会被加载,内部类要在方法调用之前初始化,巧妙地避免了线程安全问题
 *     (3) 兼顾了synchronized的性能问题
 * 缺点:
 *     反序列化,反射与克隆可破坏单例
 */
public class LazySingletonWithInnerClass {

    /** 私有化类构造器 */
    private LazySingletonWithInnerClass() {}

    /** 使用内部类定义静态私有 LazySingletonWithInnerClass 类对象 */
    private static class LazyHolder {
        private static final LazySingletonWithInnerClass INSTANCE = new LazySingletonWithInnerClass();
    }

    /** 提供公共静态的获取该私有类对象的方法 */
    public static LazySingletonWithInnerClass getInstance() {
        return LazyHolder.INSTANCE;
    }

}

静态内部类懒汉式-测试类

注册登记式

每使用一次,都往一个固定的容器中去注册并将使用过的对象进行缓存,下次去取对象的时候,就直接从缓存中取值,以保证每次获取的都是同一个对象。Spring IOC中的单例模式,就是典型的注册登记式单例。

/**
 * @author jiangsj
 * 注册登记式——map容器单例
 */
public class RegisterSingletonFromMap {

    /** 私有化类构造器 */
    private RegisterSingletonFromMap() {}

    /** 使用 ConcurrentHashMap 容器,装载 RegisterSingletonFromMap 类对象 */
    private static Map<String, RegisterSingletonFromMap> map = new ConcurrentHashMap<String, RegisterSingletonFromMap>();

    /** 提供公共静态的获取该私有类对象的方法 */
    public static RegisterSingletonFromMap getInstance() {
        String className = RegisterSingletonFromMap.class.getName();

        synchronized (RegisterSingletonFromMap.class) {
            if (!map.containsKey(className)) {
                map.put(className, new RegisterSingletonFromMap());
            }
        }

        return map.get(className);
    }

}

map容器单例-测试类

注册登记式还有一种写法,枚举型单例
/**
 * @author jiangsj
 * 注册登记式——枚举单例
 */
public class RegisterSingletonFromEnum {

    /** 私有化类构造器 */
    private RegisterSingletonFromEnum() {}

    /** 使用 enum 实例特性创建 RegisterSingletonFromEnum 类对象 */
    private enum Singleton {
        INSTANCE;

        private RegisterSingletonFromEnum instance;

        // JVM保证此方法只调用一次
        Singleton() {
            instance = new RegisterSingletonFromEnum();
        }

        public RegisterSingletonFromEnum getInstance() {
            return instance;
        }
    }

    /** 提供公共静态的获取该私有类对象的方法 */
    public static RegisterSingletonFromEnum getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

}

枚举型单例-测试类

序列化与反序列化式

序列化与反序列化式单例,需要重写readResolve()方法。

/**
 * @author jiangsj
 * 序列化与反序列化式单例
 */
public class SerializableSingleton implements Serializable {

    /** 私有化类构造器 */
    private SerializableSingleton() {}

    /** 定义静态私有类对象 */
    private static final SerializableSingleton INSTANCE = new SerializableSingleton();

    /** 提供公共静态的获取该私有类对象的方法 */
    public static SerializableSingleton getInstance() {
        return INSTANCE;
    }

    /** 重写readResolve()方法,保证反序列化生成对象时获得的是同一个对象 */
    private Object readResolve() throws ObjectStreamException {
        return INSTANCE;
    }

}

序列化与反序列化式单例-测试类

上述所有单例的测试类(序列化与反序列化式单例测试类除外)都包含如下单元测试方法:

  • concurrentGetSingletonWithCyclicBarrierTest()
  • concurrentGetSingletonWithCountDownLatchTest()
  • breakSingletonByReflectionTest()

序列化与反序列化式单例测试类包含如下测试方法:

  • serializableSingletonTest()

单例模式要点总结

  • Singleton 模式中的实例构造器可以设置为 protected 以允许子类派生。
  • Singleton 模式一般不要实现 Clone 接口,因为这有可能导致多个对象实例,与 Singleton 模式的初衷违背。
  • 如何实现多线程环境下安全的 Singleton?需注意对双检查锁的正确实现。

结束语

以上便是我对Java单例模式学习的总结,如有疏漏或错误之处,欢迎大家留言指正。

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

推荐阅读更多精彩内容