资讯专栏INFORMATION COLUMN

单例模式五种实现

Jrain / 1060人阅读

摘要:前言文章介绍了单例模式五种实现的方式,分别是懒汉,饿汉,静态内部类,双重检验锁以及枚举实现方式,并主要关心加载时机以及线程安全。

前言

文章介绍了单例模式五种实现的方式,分别是懒汉,饿汉,静态内部类,双重检验锁以及枚举实现方式,并主要关心加载时机以及线程安全。首先,通俗点讲,饿汉就是这个类还没被使用到的时候,实例已经创建好了;而懒汉是使用到的时候才创建对应的实例。线程安全方面主要考虑实例化时候是否确保一个实例,对于单例类中其他方法的线程安全不予考虑。

懒汉模式

先来一个最直观的代码:

public class Singleton {

    private static Singleton instance = null;
    private Singleton(){}

    public static Singleton getInstance(){
        //如果还没有被实例化过,就实例化一个,然后返回
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

这段代码里,我们没有考虑线程安全,所以可能就会产生多个实例,但是这个例子能很好的表达单例模式的思想,就是保持只有一个实例,先理解了基础,我们再一步步发展。

所以线程安全的事情还是得解决啊~于是有了以下的代码:

public class Singleton {

    private static Singleton instance = null;
    private Singleton(){}

    public static synchronized Singleton getInstance(){
        //如果还没有被实例化过,就实例化一个,然后返回
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

这段代码只加了一个关键字synchronized用于确保getInstance方法线程安全,但是这种方式问题很大啊,毕竟所有的线程到了这个方法全得排队等着,对性能的损耗非常大,不过没关系,我们这里着重先解决掉线程安全的问题,接下来会有办法解决这个效率低下的问题(如果你着急那就直接去看双重校验锁吧...)

懒汉模式将实例化的时机放到了需要使用的时候(饿汉是类加载了就有实例),也就是“延迟加载”,相比饿汉,能避免了在加载的时候实例化有可能用不到的实例,但是问题也很明显,我们要花精力去解决线程安全的问题。

饿汉模式

饿汉模式相比懒汉模式,在类加载的时候就已经存在一个实例,举个例子,比如数据库连接吧,懒汉就是第一次访问数据库的时候我才去创建一个连接,而饿汉呢,是你程序启动了,类加载好了的时候,我已经有个连接了,你用不用不一定了,所以饿汉的缺点也就出来了:可能会产生很多无用的实例。

public class Singleton {
    //类加载的时候instance就已经指向了一个实例
    private static Singleton instance = new Singleton();
    private Singleton(){}

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

那么加载时机的问题我们已经说过了,接下来就是线程安全了,代码里我们并没有看见synchronized关键字,那么这种方式是如何确保线程安全的呢,这个就是JVM类加载的特性了,JVM在加载类的时候,是单线程的,所以可以保证只存在单一的实例。

双重校验锁

首先要说明的是,双重检验锁也是一种延迟加载,并且较好的解决了在确保线程安全的时候效率低下的问题。

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;
    }
}

对比一下最原始的那种线程安全的方法(就是懒汉模式的第二种代码),那种方法将整个getInstance方法锁住,那么每次调用那个方法都要获得锁,释放锁,等待等等...而双重校验锁锁住了部分的代码。进入方法如果检查为空才进入同步代码块,这样很明显效率高了很多。
有人疑问为什么instance==null要判断两次吗,那我们先去掉第二次的判断。
如果两个线程一起调用getInstance方法,并且都通过了第一次的判断instance==null,那么第一个线程获取了锁,然后实例化了instance,然后释放了锁,然后第二个线程得到了线程,然后马上也实例化了instance,这就尴尬了。单例模式就失败了。
所以加上第二次判断后,先进来的线程判断了一下,哦,为空,我创建一个,然后创建一个实例之后释放了锁,第二个线程进来之后,哎?已经有了,那我就不用创建了,然后释放了锁,开开心心的完成了单例模式。

静态内部类

懒汉模式需要考虑线程安全,所以我们多写了好多的代码,饿汉模式利用了类加载的特性为我们省去了线程安全的考虑,那么,既能享受类加载确保线程安全带来的便利,又能延迟加载的方式,就是静态内部类。Java静态内部类的特性是,加载的时候不会加载内部静态类,使用的时候才会进行加载。而使用到的时候类加载又是线程安全的,这就完美的达到了我们的预期效果~

public class Singleton {

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

    private Singleton(){}

    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}
枚举

JDK1.5提供了一个新的数据类型,枚举。枚举的出现提供了一个较为优雅的方式取代以前大量的static final类型的变量。而这里,我们也利用枚举的特性,实现了单例模式,这种思路是《Effective Java》中第三条最后一段给出的实现方式,有兴趣的可以看看这本书~
代码简单到无法理解:

public enum Singleton {
    INSTANCE;
}

外部调用由原来的Singleton.getInstance变成了Singleton.INSTANCE了。

这里要注意,原来的class已经换成了关键字enum,但是其实无所谓的,看下继承关系就能知道,其实还是一个class

而且我们可以查看Enum的源码:

这里能看到实现了Serializable接口,所以不用考虑序列化的问题(其实序列化反序列化也能导致单例失败的,但是我们这里不过多研究)。对于线程安全,同样的,加载的时候JVM能确保只加载一个实例。

总结

在单例模式各种设计的方法中,我们使用到了内部静态类的特性,使用了枚举的特性,所以基础非常重要,单例模式是设计模式之一,而设计模式其实是对语言特性不足的一面进一步的包装。吸纳基础,工作学习多加思考,设计模式也就自然而然的能够理解。
另外,好多帖子都说利用枚举的方式在团队合作中不常使用,因为需要配合,用了枚举别人不熟悉。这点我是不同意的,如果因为大家不懂不熟悉而放弃了使用很棒的特性,那么就永远抱着旧的方式停滞不前,JDK的更新也失去了意义。
还是希望能够不断的接触Java新鲜的想法,在深厚的基础上迸发不一样的思路,然后去解决实际的问题。

以上,如有不妥,还望指正。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70194.html

相关文章

  • 单例模式总结

    摘要:如果是后者,则在执行完毕未执行之前,被线程二抢占了,这时已经是非了但却没有初始化,所以线程二会直接返回在之后双重检查锁定才能够正常达到单例效果,之前有个坑。所以,在版本前,双重检查锁形式的单例模式是无法保证线程安全的。 第一种(懒汉, 线程不安全): public class Singleton { private static Singleton instance; ...

    xorpay 评论0 收藏0
  • php设计模式

    摘要:总体来说设计模式分为三大类创建型模式共五种工厂方法模式抽象工厂模式单例模式建造者模式原型模式。优点一实例控制单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。 总体来说设计模式分为三大类: 创建型模式---共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式---共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式...

    cgh1999520 评论0 收藏0
  • 单例模式

    摘要:第二种懒汉式线程不安全对象为空才去实例化懒汉式是在使用的时候才会去加载,不过当多次同时去加载的时候就会存在线程安全问题。 单例模式,是一种常用的软件设计模式,在它的核心结构中只包含一个被称为单例的特殊类,通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例。 第一种:饿汉式 public class SingleEasy { private SingleEas...

    darkbug 评论0 收藏0
  • 一起学设计模式 - 单例模式

    摘要:懒汉非线程安全,需要用一定的风骚操作控制,装逼失败有可能导致看一周的海绵宝宝饿汉天生线程安全,的时候就已经实例化好,该操作过于风骚会造成资源浪费单例注册表初始化的时候,默认单例用的就是该方式特点私有构造方法,只能有一个实例。 单例设计模式(Singleton Pattern)是最简单且常见的设计模式之一,主要作用是提供一个全局访问且只实例化一次的对象,避免多实例对象的情况下引起逻辑性错...

    Keven 评论0 收藏0
  • PHP 设计模式概述

    摘要:创建型模式主要有以下五种简单工厂模式和工厂方法模式抽象工厂模式单例模式建造者模式原型模式在设计模式一书中将工厂模式分为两类工厂方法模式与抽象工厂模式。 一、 设计模式(Design pattern)是什么 设计模式是一套被反复使用、多数人知晓、经过分类编目的代码设计的经验总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 二、 为什么会有设计模式 在软件开发过...

    IntMain 评论0 收藏0

发表评论

0条评论

Jrain

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<