创建型设计模式(一)单例

单例模式是指一个类确保其只拥有一个实例对象,且此实例对象由类自己创建并提供方法为整个系统提供这个实例的访问。

一、特点

  • 实现单例模式的类只有唯一一个实例对象
  • 实现单例模式的类必须自己创建此实例对象
  • 实现单例模式的类必须向整个系统提供访问此实例对象的方法

二、代码实现

1. 饿汉式单例

单例类在加载时即实例化唯一对象。

package singleton;

public class HungrySingleton {
    
    private static final HungrySingleton singleton = new HungrySingleton();
    
    private HungrySingleton() {}
    
    public static HungrySingleton getSingleton() {
        return singleton;
    }
}

2. 懒汉式单例

单例类在系统首次调用其提供的访问实例对象的方法时实例化唯一对象。

package singleton;

import java.util.Objects;

public class LazySingleton {

    private static LazySingleton singleton;

    private LazySingleton() {}

    public static LazySingleton getSingleton() {
        if (Objects.isNull(singleton)) {
            singleton = new LazySingleton();
        }
        return singleton;
    }
}

相比于饿汉式单例,懒汉式单例更加合理,只有需要时才创建具体的实例对象而非类加载时便实例化此对象。为说明此问题请看以下代码:

package singleton;

public class HungrySingleton {
    
    private static final HungrySingleton singleton = new HungrySingleton();
    
    private HungrySingleton() {}
    
    public static HungrySingleton getSingleton() {
        return singleton;
    }
    
    public static void methodA() {
        System.out.println("Provide other service");
    }
}

在实现饿汉式单例的类中新增了一个方法methodA,该方法可以提供服务,当methodA被首次调用时,HungrySingleton便被加载并实例化了静态属性singleton,但实际上此实例对象尚未被使用,即使后续系统中一直不使用singleton实例对象,此实例对象也会常驻内存。

3. 加锁实现懒汉式单例

第 2 部分懒汉式单例代码中存在一个问题,没有考虑多线程并发场景,当多个线程同时访问getSingleton方法时可能实例化多个对象,但最终只有一个对象被赋给静态属性singleton,其它对象只能等待垃圾回收,这不免造成了系统资源浪费。处理线程并发问题最简单的方法是通过加锁实现线程同步。

package singleton;

import java.util.Objects;

public class LazySingleton {

    private static LazySingleton singleton;

    private LazySingleton() {}

    public static synchronized LazySingleton getSingleton() {
        if (Objects.isNull(singleton)) {
            singleton = new LazySingleton();
        }
        return singleton;
    }
}

以上代码中对懒汉式单例类提供的访问唯一对象实例的方法添加了一个内部锁(当然可以将synchronized同步块加在方法体内,或使用显示锁方案替换内部锁),这样就保证同时只有一个线程能够访问getSingleton方法,就不可能出现多个线程同时实例化对象的场景。

4. 双重锁单例

加锁实现懒汉式单例虽然解决了线程同步问题,但还存在缺陷,即singleton在实例化成功后,后续如果有多个线程同时调用getSingleton方法试图获取singleton的引用,这些线程仍需串行执行,由此造成性能瓶颈。解决此问题的方法是采用双重锁单例。

package singleton;

import java.util.Objects;

public class LazySingleton {
    
    private static LazySingleton singleton;
    
    private LazySingleton() {}
    
    public static LazySingleton getSingleton() {
        if (Objects.isNull(singleton)) {
            synchronized (LazySingleton.class) {
                if (Objects.isNull(singleton)) {
                    singleton = new LazySingleton();
                }
            }
        }
        return singleton;
    }
}

双重锁解决了多线程获取单例对象的性能问题,只有对象第一次实例化时会使用线程同步,一旦实例化成功,后续调用getSingleton获取对象实例都无需再次同步执行。

5. 增加关键字volatile

双重锁单例仍然存在发生异常的可能。原因是,为了提升执行效率,Java 虚拟机可能会对synchronized块中代码进行重排序。虽然singleton = new LazySingleton();看似一个语句,实际执行过程中会分为几个步骤完成:

  1. 分配内存区域
  2. 在已分配内存区域进行对象实例化
  3. 将实例化对象引用赋值给singleton

Java 虚拟机在执行过程中可能会对上诉步骤 2 和 3 进行重排序,即先将内存区域引用赋值给singleton然后再实现对象实例化,如果在对象实例化前一个线程调用getSingleton方法,则此时singleton不为null,但实际上对象实例化并未完成,该线程可能会拿到一个未实例化完的对象引用调用其提供的实例方法,很明显因为对象并未完成实例化,所以此时的实例方法调用肯定会出现异常。

为解决此问题,可以在静态属性singleton上加上关键字volatile,这会阻止 Java 虚拟机的重排序行为,保证对象是先实例化后再赋值给singleton

package singleton;

import java.util.Objects;

public class LazySingleton {
    
    private static volatile LazySingleton singleton;
    
    private LazySingleton() {}
    
    public static LazySingleton getSingleton() {
        if (Objects.isNull(singleton)) {
            synchronized (LazySingleton.class) {
                if (Objects.isNull(singleton)) {
                    singleton = new LazySingleton();
                }
            }
        }
        return singleton;
    }
}

6. 使用一个局部变量实例化对象,然后将此局部变量赋值给 singleton

除使用关键字volatile外,还可以通过局部变量实例化对象赋值的方法解决指令重排带来的问题。

package singleton;

import java.util.Objects;

public class LazySingleton {
    
    private static LazySingleton singleton;
    
    private LazySingleton() {}
    
    public static LazySingleton getSingleton() {
        if (Objects.isNull(singleton)) {
            synchronized (LazySingleton.class) {
                if (Objects.isNull(singleton)) {
                    LazySingleton temp = new LazySingleton();
                    singleton = temp;
                }
            }
        }
        return singleton;
    }
}

7. 使用静态内部类实现懒汉式单例

还有一种更加简单的单例实现方法,利用了 Java 静态内部类的机制,当 LazySingleton 被加载时并不会触发其静态内部类 Holder 的加载,只有首次调用 getSingleton 方法时才会加载静态内部类 Holder,又因 Holder 的静态属性 singletonfinal,所以只会被实例化一次。

package singleton;

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

推荐阅读更多精彩内容