单例(Singleton)模式

本文详细介绍了单例模式的五种实现方式,包括饿汉模式、懒汉模式、DoubleCheckLock模式、静态内部类模式和枚举模式,并分析了每种模式的特点及适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  用我自己惨痛的经历说一下优快云的这个Markdown编辑器。现在是我第三遍码这篇博客,之前电脑还没配环境写了一个开头扔草稿箱了,昨晚开始继续码,码了两遍。第一遍不小心敲到了F5,界面就只剩下原来存在草稿箱里面的内容了,不甘心,码第二遍,写到最后准备挂上Github地址,一下忘记怎么给文字加颜色了,打开以前的一篇博客,又启开一个MarkDown,再切回来就傻逼了,当前页面正在试图关闭窗口,是,否,我选择了否,然后就是这样一张图,
  这里写图片描述
图后面的文字是我之前写的,所以为了演示这个又码了一遍开头。点击重新加载之后就有Game Over了。“Markdown编辑器已经停止,因为另一个实例中相同的浏览器中运行”,好像有点病句,我们把一个浏览器看成一个系统,显然在这个系统中,Markdown编辑器是单例模式的,只允许存在唯一一个实例,这就是今天要说的单例模式。

  单例模式应该算是最最简单的一种设计模式了,也是应用很广的一种设计模式。顾名思义,在系统中单例模式的对象必须保证只有一个实例存在,比如一个Android程序中只存在一个Application对象,或者说一个进程中只存在一个Application对象。还有InputMethodManager,日历,短信模块,开源项目ImageLoader等等,在Android中有很多单例模式的具体应用。那么我们为什么要去使用单例模式呢,Java中创建对象和销毁对象都是很耗资源的,对于一些创建对象需要消耗很多资源的场景,比如ImageLoader,涉及到线程池,缓存,网络请求等,我们没有必要频繁的去创建对象,只需要向整个系统提供一个实例就可以了。实现单例模式比较简单,但是有一些需要注意的地方。书中总结了一下几个关键点:
  
(1)一般使用私有的构造函数,使外界不能通过new来创建对象。
(2)通过一个静态方法或者枚举返回单例类对象。
(3)确保单例类的对象只有一个,尤其是在多线程情况下,考虑线程安全问题。
(4)确保单例类在反序列化时不会重新创建对象。

  下面介绍几种典型的单例模式的实现形式。

1.饿汉模式
public class Singleton {

    public static final Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}

  饿汉模式的实现方式很简单,在加载类的时候就已经实例化了该类的一个静态对象,天生就是线程安全的。但是没有考虑延时加载,什么是延时加载呢,也叫懒加载,就是在需要的时候再去实例化对象,比如说在调用getInstance方法的时候。这样就可能造成某些情况下的不必要的资源消耗。

2.懒汉模式
public class Singleton {

    private Singleton(){}

    private static Singleton instance=null;

    public static synchronized Singleton getInstance(){
        if (instance==null){
             instance=new Singleton();
        }
        return instance;
    }
}

  懒汉模式弥补了饿汉模式的缺点,使用了在延时加载,加载类的时候不会去实例化对象,第一次调用getInstance方法的时候才去初始化instance对象。但是这么做带来的高并发情况下的线程安全问题是不可避免的,难以保证系统中实例是唯一存在的,违背了单例模式的原则。这里使用了synchronized关键字,看起来解决了线程安全的问题,但是每次调用getInstance方法都去进行同步操作,这样带来了很多不必要的同步开销,毕竟大多数情况下我们是不需要进行同步操作的。所以,这种模式的单例模式是不推荐使用的。

3.Double CheckLock模式
public class Singleton {

    private static Singleton instance = null;

    private Singleton() {}

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

  Double CheckLock模式,听着名字挺高大上,取个中文名叫双检查锁模式,其实看看代码还是很好理解的,可以看成是懒汉模式的加强版。这里同样使用了延时加载,调用getInstance方法的时候才会去实例化对象,getInstance方法是这里的重点。里面对instance对象进行了两层非空的检查,第一层判断当instance非空时不会去进行同步操作,避免了不必要的消耗,第二层判断是为了在instance为空时进行实例化对象。看起来一切都是这么美好,延时加载,线程安全,同时避免了不必要的同步操作。但是,由于Java内存模型的原因,在高并发环境下偶尔会出现失效问题,具体原因就不在这里阐述了,有兴趣的可以看看Android源码设计模式29页的解释(码第三遍,页数都记住了。。)。

4.静态内部类模式
public class Singleton {

    private Singleton(){}

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

    public static Singleton getInstance(){
        return InstanceHolder.instance;
    }
}

  静态内部类单例模式是比较推荐的一种方式。当第一次调用getInstance方法,加载静态内部类InstanceHolder的时候才去实例化对象,延时加载。同时也是线程安全的,保证了实例在系统中的唯一存在。

5.枚举模式
public enum Singleton{
  INSTANCE
}

  枚举模式的有点,够简单粗暴,而且枚举的创建天生就是线程安全的。搜索了一下,很多人都是极力推荐这种方法,但是总是看起来有点怪怪的,可能是不怎么经常使用枚举的原因吧,看不到一点类的味道。

  最后总结一下,不管是哪种模式,要注意的都是一下几点:
  (1)私有的构造方法
  (2)提供一个静态方法向外界提供实例
  (3)保证在系统中实例是唯一存在的
  (4)注意在高并发环境下的线程安全问题
  (5)使用延时加载
 
 示例代码托管地址:https://github.com/lulululbj/JavaDesignPatterns

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值