美文网首页@IT·互联网
设计模式之(一)单例模式

设计模式之(一)单例模式

作者: Hi夜城 | 来源:发表于2016-07-19 14:02 被阅读114次

    单例模式(Singleton Pattern)是 编程语言中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

        单例模式的意图在于:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

        何时使用:当您想控制实例数目,节省系统资源的时候。

        使用场景: 

          1、要求生产唯一序列号。

          2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 

          3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

        优点: 

          1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 

          2、避免对资源的多重占用(比如写文件操作)。 

        缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

    为了更好便于理解代码的运行机制,请先耐住性子看完以下的知识点:

    涉及知识点:线程锁

          ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,

          如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情 

    设计知识点:静态成员|静态方法|静态构造函数

    一、静态成员(static)

    (1) 静态成员属于类所有,非静态成员属于类的实例所有。

    (2) 每创建一个类的实例,都会在内存中为非静态成员新分配一块存储;非静态成员属于类所有,为各个类的实例所公用,无论类创建了多少实例,类的静态成员在内存中只占同一块区域。

    二、静态方法

    (1) 静态方法属于类所有,类实例化前即可使用。

    (2) 非静态方法可以访问类中的任何成员,静态方法只能访问类中的静态成员。因为静态方法在类实例化前就可以使用,而类中的非静态变量必须在实例化之后才能分配内存,这样,静态方法调用时无法判断非静态变量使用的内存地址。所以无法使用.而静态变量的地址对类来说是固定的,故可以使用。

    三、静态构造函数

    (1) 用于对静态字段、只读字段等的初始化。

    (2) 添加static关键字,不能添加访问修饰符,因为静态构造函数都是  私自有的。

    (3) 类的静态构造函数在给定应用程序域中至多执行一次:只有创建 类的实例或者引用类任何静态成员才激发静态构造函数 .

    (4) 静态构造函数是不可继承的,而且不能被直接调用。

    (5) 如果类中包含用来开始执行的 Main 方法,则该类的静态构造函 数将在调用 Main 方法之前执行。任何带有初始值设定项的静态字段,则在执行该类的静态构造函数时,先要按照文本顺序执行那些初始值设定项。

    (6) 如果没有编写静态构造函数,而这时类中包含带有初始值设定的静态字段,那么编译器会自动生成默认的静态构造函数。

    (7) 无参数的构造函数可以与静态构造函数共存。尽管参数列表相同, 但一个属于类,一个属于实例,所以不会冲突。  MSDN对静态构造函数的解释:   静态构造函数用于初始化任何静态数据,或用于执行仅需执行一次的特定操作。在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数。

    静态构造函数具有以下特点:

    静态构造函数既没有访问修饰符,也没有参数。

    如何使用:

        第一步: 创建一个 Singleton 类。

    publicclassSingleObject

    {

        //创建 SingleObject 的一个对象

        privatestaticSingleObjectinstance=newSingleObject();

        //让构造函数为 private,这样该类就不会被实例化

        privateSingleObject(){}

        //获取唯一可用的对象

        publicstaticSingleObjectgetInstance()

        {

            returninstance;

        }

        publicvoidshowMessage()

        {

            System.out.println("Hello World!");

        }

    }

        第二步:从 singleton 类获取唯一的对象。

    publicclassSingletonPatternDemo

    {

        publicstaticvoidmain(String[]args)

        {

            //不合法的构造函数

            //编译时错误:构造函数 SingleObject() 是不可见的

           //SingleObject object = new SingleObject();

            //获取唯一可用的对象

            SingleObjectobject=SingleObject.getInstance();

            //显示消息object.showMessage();

        }

    }

        第三部:验证输出。

        Hello World!

    单例模式的几种实现方式

    单例模式的实现有多种方式,如下所示:

    1、懒汉式,线程不安全

    是否 Lazy 初始化:

    是否多线程安全:

    实现难度:

    描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

    这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

    代码实例:

    public class Singleton

     {

        private static Singleton instance;

        private Singleton (){}

        public static Singleton getInstance() {

        if (instance == null) 

        {

            instance = new Singleton();

        }

        return instance;

        }

    }

    接下来介绍的几种实现方式都支持多线程,但是在性能上有所差异。

    2、懒汉式,线程安全

    是否 Lazy 初始化:

    是否多线程安全:

    实现难度:

    描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。

    优点:第一次调用才初始化,避免内存浪费。

    缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

    getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

    涉及知识点:线程锁

    代码实例:

    public class Singleton

     {

        private static Singleton instance;

        private Singleton (){}

        public static synchronized Singleton getInstance() {

        if (instance == null)

        {

            instance = new Singleton();

        }

        return instance;

        }

    }

    3、饿汉式

    是否 Lazy 初始化:

    是否多线程安全:

    实现难度:

    描述:这种方式比较常用,但容易产生垃圾对象。

    优点:没有加锁,执行效率会提高。

    缺点:类加载时就初始化,浪费内存。

    它基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

    代码实例:

    public class Singleton

     {

        private static Singleton instance = new Singleton();

        private Singleton (){}

        public static Singleton getInstance()

        {

            return instance;

        }

    }

    4、双检锁/双重校验锁(DCL,即 double-checked locking)

    JDK 版本:JDK1.5 起

    是否 Lazy 初始化:

    是否多线程安全:

    实现难度:较复杂

    描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

    getInstance() 的性能对应用程序很关键。

    代码实例:

    public class Singleton

     {

        private volatile static Singleton singleton;

        private Singleton (){}

        public static Singleton getSingleton()

        {

            if (singleton == null)

            {

                synchronized (Singleton.class)

                 {

                       if (singleton == null)

                       {

                          singleton = new Singleton();

                       }

                 }

            }

        return singleton;

       }

    }

    5、登记式/静态内部类

    是否 Lazy 初始化:

    是否多线程安全:

    实现难度:一般

    描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

    这种方式同样利用了 classloder 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。

    代码实例:

    public class Singleton

     {

        private static class SingletonHolder 

        {

            private static final Singleton INSTANCE = new Singleton();

        }

        private Singleton (){}

        public static final Singleton getInstance() {

        return SingletonHolder.INSTANCE;

        }

    }

    6、枚举

    JDK 版本:JDK1.5 起

    是否 Lazy 初始化:

    是否多线程安全:

    实现难度:

    描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

    这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

    不能通过 reflection attack 来调用私有构造方法。

    代码实例:

    public enum Singleton 

    {

        INSTANCE;

        public void whateverMethod()

        {

        }

    }

    经验之谈:一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

    相关文章

      网友评论

        本文标题:设计模式之(一)单例模式

        本文链接:https://www.haomeiwen.com/subject/hppmjttx.html