Java设计模式——单例模式

Java设计模式——单例模式

单例模式应该是大家最为熟知的一种设计模式了,相信大家或多或少的都在自己的项目中使用过单例模式,例如封装一个Log工具类、一个数据库存取类或者用户登录管理类等。而我们使用单例模式主要有两个目的:
1.减少内存消耗
2.多个线程中保证共享资源的唯一性
单例模式的写法有好多种,如:饿汉式单例模式、懒汉式单例模式、IoDH单例模式、枚举式单例模式,下面就来一一实现下这些写法。

饿汉式单例模式

public class HungrySingletonV1 {
    private static HungrySingletonV1 instance = new HungrySingletonV1();
    private HungrySingletonV1(){
    }
    public static HungrySingletonV1 getInstance(){
        return instance;
    }
}

上面的例子中,类被加载时,静态变量instance就会被初始化,这时候单例类的唯一实例就被创建了。

优点:类加载时就已经实例化,避免了线程问题
缺点:由于类加载的时候就实例化了,没有达到懒加载的效果,可能会造成内存浪费
饿汉式单例模式还要一种变种

public class HungrySingletonV2 {
    private static HungrySingletonV2 instance;
    static {
        instance = new HungrySingletonV2();
    }
    private HungrySingletonV2(){
    }
    
    public static HungrySingletonV2 getInstance(){
        return instance;
    }
}

效果与前面的一样,不过把初始化方法放到静态代码块中,也是在类加载时调用。

懒汉式单例模式

低配版

public class LazySingletonV1 {
    private static LazySingletonV1 instance ;
    private LazySingletonV1(){
    }
    public static LazySingletonV1 getInstance(){
        if(instance == null){
            instance = new LazySingletonV1();
        }
        return instance;
    }
}

上面的代码是最简单的懒汉式单例模式,实现懒加载,每次获取实例时会去判断是否已创建实例,如果一直没人用,则不用创建实例,节省内存空间。

但实际上,上面这种写法应该是最不推荐的一种单例模式写法。因为它是线程不安全的,如果多个线程同时获取该实例,就会创建多个实例对象,不符合单例的需求。

进阶版

public class LazySingletonV2 {
    private static LazySingletonV2 instance ;
    private LazySingletonV2(){
    }
    public static synchronized LazySingletonV2 getInstance(){
        if(instance == null){
            instance = new LazySingletonV2();
        }
        return instance;
    }
}

可以看到在这种懒汉式单例模式中,我们在获取实例的方法上加了一个同步锁,这样保证了获取实例的方法在不同线程中是同步的,使得获取的实例是唯一的。

但是这种写法有一个最大的问题就是,效率太低。每一个线程都要进行等待,而实际中,如果已经创建了,后面的想获取实例,直接返回就行。

再进阶版

public class LazySingletonV3 {
    private volatile static LazySingletonV3 instance;
    private LazySingletonV3() {
    }
    public static LazySingletonV3 getInstance() {
        if (instance == null) {
            synchronized (LazySingletonV3.class) {
                instance = new LazySingletonV3();
            }
        }
        return instance;
    }
}

基于前一个版本的问题,有人想到了将同步锁置于instance为空判断之后,这样就实现了当实例已经创建,后面的获取时直接返回的问题。

但是,这种写法又导致了线程不安全。如果A用户拿到了同步锁,正在创建实例,另一个B用户在实例还未创建时到了同步锁外等候,当A用户创建完实例,退出同步锁后,B用户马上就获取了同步锁并开始创建实例,这就导致了创建了多个实例。

终极版

//双重校验锁
public class LazySingletonV4 {
    private  volatile static LazySingletonV4 instance;
    private LazySingletonV4(){
    }
    public static LazySingletonV4 getInstance(){
        //检查实例是否存在,不存在才进入同步块
        if(instance == null){
            //同步块,保证线程安全
            synchronized(LazySingletonV4.class){
                //再次检查实例是否存在,不存在才创建实例
                if(instance == null){
                    instance = new LazySingletonV4();
                }
            }
        }
        return instance;
    }
}

双重校验算是懒汉式单例模式的终极版本了,先判断实例是否为空,为空获取同步锁,在同步锁内再判断一次实例是否为空,既保证了线程安全又提高了效率。

双重校验加锁的实现一般会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。

IoDH单例模式

什么是IoDH呢?

IoDH是Initialization Demand Holder 的缩写,简单来说就是在单例类中增加一个静态内部类,在该内部类中创建单例类的实例。

我们知道,在多线程开发时,为了解决并发问题,我们会使用synchronized来加互斥锁进行同步控制。但是在某些情况下,JVM已经隐含为您执行了同步,这些时候就不需要自己进行同步控制了。这些情况包括:

由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
访问final字段时
在创建线程之前创建对象时
线程可以看见它将要处理的对象时
例如饿汉式单例模式,就是在类加载时进行了初始化,也就是由静态初始化器初始化的。但是饿汉式单例模式不符合懒加载的要求,如果可以让类加载时不去初始化对象,不就解决问题了吗。这就是IoDH的方法,通过定义一个静态内部类,在这个静态内部类中创建单例类实例,当我们需要使用时才会去加载这个静态内部类,创建单例类实例。

public class IoDHSingleton {
    private IoDHSingleton() {
    }
    private static class SingletonHolder {
        /**
         * 静态初始化器,由JVM来保证线程安全
         */
        private static IoDHSingleton instance = new IoDHSingleton();
    }
    public static IoDHSingleton getInstance() {
        return SingletonHolder.instance;
    }
}

当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建IoDHSingleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。

这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

枚举式单例模式

借助JDK1.5中添加的枚举来实现单例模式,应该是最好的实现单例模式的方式,代码也很简单。

public enum EnumSingleton {
    instance;
    public void method(){
        //功能方法
    }
}

访问也很简单,通过EnumSingleton.instance即可调用枚举类中的方法了。这种单例模式不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

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

推荐阅读更多精彩内容

  • 概念 java中单例模式是一种常见的设计模式,单例模式的写法有好几种,比较常见的有:懒汉式单例、饿汉式单例。单例模...
    怡红快绿阅读 443评论 0 0
  • 单例模式是设计模式中使用最普遍的设计模式之一。它是一种对对象创建模式,主要用于产生一个对象的具体唯一实例,它可以确...
    凡星轨迹阅读 738评论 0 49
  • 阅读原文 在介绍单例模式之前,我们先了解一下,什么是设计模式?设计模式(Design Pattern):是一套被反...
    gyl_coder阅读 135评论 0 3
  • 单例模式在我们日常开发中算是使用频率最高的设计模式了,为什么单例模式的使用频率会这么高呢? 有时我们的对象会占用一...
    MrKing5946阅读 141评论 0 0
  • 人生有多种精彩,你不开始,永远无法走到终点。 1. 念一记得她小时候养过一只小狗,浑身雪白雪白,两只眼睛骨碌骨碌的...
    许沐笙阅读 551评论 1 10