【23种设计模式之一】单例设计模式(翻译)

引言:

    这一系列文章,翻译自网络上的文章,不过中间会夹杂着个人的理解,非原创,不过中文应该算是原创。

    下面介绍,使用设计模式的一些好处:

    1、设计模式是已经在工业生产中使用的,用于解决特定问题的标准方法,如果你在遇到类似问题的时候,能参考相应的设计,会节省你很多的时间。

    2、设计模式,往往能提高代码的重用性,会减少你的开发时间。

    3、既然大家都知道设计模式,包括从你的类的命名上面,类的使用上面,大家都很清楚的知道相应类的具体功能,就像每个人都约定好的那样。

 分类:

    java设计模式分为3大类:

    1、对象创建

    2、结构

    3、行为

对象创建设计模式的定义

    1、在特定的问题场景中,创建设计模式会合理的根据特定的问题创建对象。因为对象的创建可能会很复杂,会增加不必要的复杂性,创建设计模式以不同的创建方法解决了这些复杂性。

对象创建设计模式包含的范围:

    1、单例

    2、工厂

    3、抽象工厂

    4、建造者

    5、原型

    本节会讲一下,对象创建中的单例设计模式。

    单例设计模式,大部分人第一印象感觉很简单,看完这篇博客,我想你就不会这么认为啦,不同的开发者对于以何种方式实现单例设计模式是有争议的,本文尽可能的把目前实现单例的方法概括出来,并给出最佳实战的建议。

单例模式的定义:

    单例模式是一种创建对象实例的设计模式,在创建对象的过程中,他会保证,在一个java虚拟机里面只有一个对象实例,同时单例设计模式,必须提供一个给外部访问该唯一对象的入口。

单例模式的应用场景:

    日志、驱动对象、缓存、线程池等等,当然设计模式不是单一存在的,其它设计模式中也会使用单例设计模式来完成一些事情,比如:抽象工厂,建造者,原型,门面等。同时在java核心包里面也有其身影,比如java.lang.runtime.

单例模式的实现方法的共性:

    1、提供私有的构造函数,防止外部类直接初始化该对象

    2、提供私有的静态变量,当然要是该类的对象实例,唯一的对象实例。

    3、public的静态方法,这就是上面说的,提供给外部类获取该类对象的唯一入口。

接下来,我们会以不同的方法,来实现单例设计模式。

【A】饥饿实现

    饥饿实现就是,等不急,当类被classloader加载的时候就初始化了该对象,这种实现有个弊端就是,当client不使用该类实例就浪费啦,不过我个人觉得,这种情况不存在,你不用他写他做什么呢?代码测试要有覆盖率的,测试天天在你后面催着喊你把他删掉。

public class EagerInitializedSingleton 
{
     private static final EagerInitializedSingleton instance = new
     EagerInitializedSingleton();
    //private constructor to avoid client applications to use constructor
     private EagerInitializedSingleton(){}
     public static EagerInitializedSingleton getInstance()
     {
        return instance;
     }
}

    如果你的单例对象在创建的时候不使用过多的资源,这种方法是可行的。但是大部分的时候,单例对象创建的时候会加载很多的资源文件,比如File system,数据库连接等等。我们应该避免在客户端调用getInstance方法之前创建这个对象,但是这种方法无法提供异常的捕获,异常的抛出,可能在创建对象的时候。

【B】静态块的实现方式

    静态块的实现方式和上面的实现方式差不多,但是静态块的实现,可以处理异常。代码如下:

public class StaticBlockSingleton
{
    private static StaticBlockSingleton instance;
    private StaticBlockSingleton(){}
    //static block initialization for exception handling
    static
    {
        try{
            instance = new StaticBlockSingleton();
           }catch(Exception e)
           {
                throw new RuntimeException("Exception occured in creating singleton instance");
           }
    }
    public static StaticBlockSingleton getInstance()
    {
        return instance;
    }
}

    和饥饿的实现方式一样,当类被classloader加载的时候就初始化了该对象。

【C】延迟创建

    顾名思义,就是需要的时候再去创建。

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

    上面这种实现方法,在单线程的环境下是工作良好的,但是,在多线程并发创建对象的时候会出现问题,简单的分析下原因,考虑多线程并发安全的时候,首先要找到共享点,就是共享的对象,然后想象各种执行顺序,你会发现instance这个地方就是共享点,当多线程执行的时候,有可能会创建多个对象,所以该方法不能保证单例实现。

【D】线程安全的创建

    为了解决多线程并发创建对象的问题,我们引入Synchronized关键字,这样的话,同一时刻,只有一个对象能访问getInstance方法。

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

    这种方式是能防止多并发引起的多对象创建问题,但是写代码嘛,总要追求点东西,你说别人装逼也好,卖弄也罢,总之在某一方面比你好。在多并发中,HashMap和ConcurrentHashMap,你是锁住整个HashMap还是HashMap的一个segment这是有质的区别的,所以改进的代码,只朝着一个方向,那就是降低锁的力度,于是乎,下面的代码使用double check来降低了力度,提高了性能。

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

还有一种实现叫Bill Pugh,java的版本更新变化很快,包括新出的java9,可能名字不是,大家也知道,金融在未来几年是个很火的领域,于是乎java9中集成了货币的api支持,这是我的猜测,哈哈,包括先在的招财宝,铜板街,挖财,据说王福强大神,跳到挖财啦,当然我也是做金融行业的业务,有兴趣的可以留言,据说要招人。这些题外话,但是不无用处哈,java的内存模型会导致上面的几种方法,在很多很多线程出现的时候,会出现问题,于是乎,这个人,也就是Bill Pugh自己实现了一下这种方法:

【E】Bill Pugh实现

public class BillPughSingleton 
{
    private BillPughSingleton(){}
    private static class SingletonHelper
    {
        private static final BillPughSingleton INSTANCE = new
        BillPughSingleton(); 
    }
    public static BillPughSingleton getInstance()
    {
        return SingletonHelper.INSTANCE;
    }
}

看代码发现Bill Pugh是通过私有静态内部类来实现的,当单例对象被classloader加载的时候,SingletonHelper是不会被加载到内存的,除非有对象调用getInstance方法,这是最常用的创建单例对象的方法,因为不需要锁,已经在多个项目中使用了这种方法,简单有效。

【F】Enum实现

public enum EnumSingleton 
{
    INSTANCE;
    public static void doSomething()
    {
        //do something
    }
}

    世界上好人和坏人总是都存在的,单例对象也不例外,总有那么几种方法是搞破坏的,费尽千辛万苦,创建的单例,有可能被破坏的。

    【破坏者1】反射

    【破坏者2】序列化

有破坏办法,就又解决办法,破坏者1的解决办法是ENUM,破坏者2的解决办法是重写readResolve方法。这两部分,你说是和反射有关呢,还是和序列化有关呢,序列化都够我写一篇东西的了。下次再说

                                        poke holes in the abstraction,and it starts leaking.

转载于:https://my.oschina.net/lianlupeng/blog/367377

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值