设计模式读书笔记一 单例模式

1、单例模式介绍
单例模式是应用最广泛的模式之一,也是可以说是初级工程师唯一会用的设计模式。在应用这一模式的时候,单例对象的类必须保证只有一个实例存在。许多时候整个系统只要一个全局对象,这样有利于我们协调系统整体行为。如在一个应用中,应该只有一个ImageLoader实例,这个ImageLoader中又包含有线程池、缓存系统、网络请求等,很消耗资源,因此,没有理由让它有多个实例。这种情况就是单例模式的使用场景。
2、单例模式的定义
确保类只有一个实例,而且自行实例化并向整个系统提供这个实例
3、使用场景
确保类只有一个对象的场景
4、UML类图

5、单例模式的多种实现
(1)饿汉模式
这是单例模式的最简单的一种写法,在具体介绍之前我们写一个简单的例子,这样也方便后面介绍单例模式的其他写发。
例如:一个公司只有一个CEO、几个VP、无数个员工,例子很简单。下面我们一点点的实现

普通员工类:Staff.java

package com.example.singleton;

public class Staff {
       public void work()
      {
             //干活
      }

}

副总裁类:VP.java

package com.example.singleton;

public class VP extends Staff {
       @Override
       public void work() {
             // TODO Auto-generated method stub
             super .work();
             //管理下面的经理
      }

}

CEO类:CEO.java

package com.example.singleton;

public class CEO extends Staff {

       private static final CEO mCeo= new CEO();

       // 构造函数私有化(构造方法的私有化是单例模式的核心)
       private CEO() {

      }

       /**
       * 方法一
       * 饿汉单例模式
       *
       * @author HP
       *
       */
       public static CEO getCeo () {
             return mCeo ;
      }
       @Override
       public void work() {
             // TODO Auto-generated method stub
             super .work();
             // 管理 vp
      }

}

公司类:Company.java

package com.example.singleton;

import java.util.ArrayList;
import java.util.List;

/**
 * 公司类
 * @author HP
 *
 */
public class Company {

    private List<Staff> allStaffs=new ArrayList<>();

    public void addStaff(Staff per)
    {
        allStaffs.add(per);
    }
    public void showAllStaffs()
    {
        for(Staff per:allStaffs)
        {
            System.out.println("Obj:"+per.toString());
        }
    }
}

最后是Main.java

package com.example.singleton;

public class Main {

       /**
       * @param args
       */
       public static void main(String[] args) {
             // TODO Auto-generated method stub
            Company cp = new Company();
             //方法一 :饿汉模式
            Staff ceo1 = CEO. getCeo();
            Staff ceo2 = CEO. getCeo();

            cp.addStaff(ceo1);
            cp.addStaff(ceo2);

            cp.showAllStaffs();
            System. out .println("--------------------------------------" );
             //测试发现有两条数据是一样的,说明单例类只能提供一个对象

            Staff vp1= new VP();
            Staff vp2= new VP();

            Staff staff1= new Staff();
            Staff staff2= new Staff();
            Staff staff3= new Staff();


            cp.addStaff(vp1);
            cp.addStaff(vp2);
            cp.addStaff(staff1);
            cp.addStaff(staff2);
            cp.addStaff(staff3);

            cp.showAllStaffs();

      }

}

输出结果:

Obj:com.example.singleton.CEO@1cacd5d4
Obj:com.example.singleton.CEO@1cacd5d4
--------------------------------------
Obj:com.example.singleton.CEO@1cacd5d4
Obj:com.example.singleton.CEO@1cacd5d4
Obj:com.example.singleton.VP@170a6001
Obj:com.example.singleton.VP@2a24ed78
Obj:com.example.singleton.Staff@5e6276e5
Obj:com.example.singleton.Staff@126be4cc
Obj:com.example.singleton.Staff@697a1686

以上就是这个例子的全部内容,其中CEO.java就是一个单例类,因为之前说了一个公司就只有一个CEO。从代码中可以看到,CEO类不能通过new关键字来构造对象,因为构造方法已经被私有化。只能通过CEO类对外开放的getCeo方法来获取对象,而这个对象是在申明的时候就已经被初始化,这就保证的对象的唯一性。以上单例模式的写法就是所谓的饿汉模式。
(2)懒汉模式
懒汉模式与饿汉模式的区别在于,懒汉模式的对象是在调用了getInstance方法的时候初始化的。懒汉模式的实现方式如下,将CEO类修改。

package com.example.singleton;

public class CEO extends Staff {

       // private static final CEO mCeo=new CEO();
       private static CEO mCeo;

       // 构造函数私有化(构造方法的私有化是单例模式的核心)
       private CEO() {

      }

       // /**
       // * 方法一
       // * 饿汉单例模式
       // *
       // * @author HP
       // *
       // */
       // public static CEO getCeo() {
       // return mCeo;
       // }
       /**
       * 方法二
       * 懒汉模式
       * @return
       */
       public static synchronized CEO getInstance() {
             if (mCeo != null) {
                   mCeo = new CEO();
            }
             return mCeo ;
      }

       @Override
       public void work() {
             // TODO Auto-generated method stub
             super .work();
             // 管理 vp
      }

}

对应的修改Main.java

package com.example.singleton;

public class Main {

       /**
       * @param args
       */
       public static void main(String[] args) {
             // TODO Auto-generated method stub
            Company cp = new Company();
//          //方法一 :饿汉模式
//          Staff ceo1 = CEO.getCeo();
//          Staff ceo2 = CEO.getCeo();

             //方法二:懒汉模式
            Staff ceo1 = CEO. getInstance();
            Staff ceo2 = CEO. getInstance();



            cp.addStaff(ceo1);
            cp.addStaff(ceo2);

            cp.showAllStaffs();
            System. out .println("--------------------------------------" );
             //测试发现有两条数据是一样的,说明单例类只能提供一个对象

            Staff vp1= new VP();
            Staff vp2= new VP();

            Staff staff1= new Staff();
            Staff staff2= new Staff();
            Staff staff3= new Staff();


            cp.addStaff(vp1);
            cp.addStaff(vp2);
            cp.addStaff(staff1);
            cp.addStaff(staff2);
            cp.addStaff(staff3);

            cp.showAllStaffs();

      }

}

懒汉模式中的getInstance方法中添加了synchronized关键字,也就是说getInstance是一个同步方法,这就是在多线程情况下保证对象唯一性的手段。但是有一个问题,那就是即使mCeo已经被初始化,每次调用getInstance方法的时候还是会同步,这样就造成了不必要的资源浪费。这也是懒汉模式的最大问题所在。

△ 懒汉模式的优缺点
懒汉模式的优点是单例只有使用的时候才会被初始化,在一定程度上节约了资源;缺点是第一次加载的时候反应稍慢,最大了问题是每次调用getInstance方法的时候都要同步,会造成不必要的开销。

(3)DCL实现方式
这种方式既可以实现在需要的时候在初始化实例,又保证线程安全,并且在调用getInstance方法的时候不同不同,其实现如下:

package com.example.singleton;

public class CEO extends Staff {

//     private static final CEO mCeo=new CEO();
       private static CEO mCeo;

       // 构造函数私有化(构造方法的私有化是单例模式的核心)
       private CEO() {

      }

//     /**
//     * 方法一
//     * 饿汉单例模式
//     *
//     * @author HP
//     *
//     */
//     public static CEO getCeo() {
//     return mCeo;
//     }
//    /**
//     * 方法二
//     * 懒汉模式
//     * @return
//     */
//    public static synchronized CEO getInstance() {
//          if (mCeo == null) {
//                mCeo = new CEO();
//          }
//          return mCeo;
//    }

       /**
       * 方法三
       * DCL
       */
       public static CEO getInstance() {
             if (mCeo == null) {
                   synchronized (CEO.class ) {
                         if (mCeo == null) {
                               mCeo =new CEO();
                        }
                  }
            }
             return mCeo ;
      }

       @Override
       public void work() {
             // TODO Auto-generated method stub
             super .work();
             // 管理 vp
      }

}

这不是一种被推荐使用的单例模式写法
(4)静态内部类实现单例模式
其实现如下:

package com.example.singleton;

public class CEO extends Staff {

       // private static final CEO mCeo=new CEO();
       // private static CEO mCeo;

       // 构造函数私有化(构造方法的私有化是单例模式的核心)
       private CEO() {

      }

       // /**
       // * 方法一
       // * 饿汉单例模式
       // *
       // * @author HP
       // *
       // */
       // public static CEO getCeo() {
       // return mCeo;
       // }
       // /**
       // * 方法二
       // * 懒汉模式
       // * @return
       // */
       // public static synchronized CEO getInstance() {
       // if (mCeo == null) {
       // mCeo = new CEO();
       // }
       // return mCeo;
       // }

       // /**
       // * 方法三
       // * DCL
       // */
       // public static CEO getInstance() {
       // if (mCeo==null) {
       // synchronized (CEO.class) {
       // if (mCeo==null) {
       // mCeo=new CEO();
       // }
       // }
       // }
       // return mCeo;
       // }

       /**
       * 方法四 静态内部类单例模式(推荐使用的单例模式)
       *
       * @return
       */
       public static CEO getInstance() {
             return SinletonCEO. mCeo;
      }

       private static class SinletonCEO {
             private static final CEO mCeo = new CEO();
      }

       @Override
       public void work() {
             // TODO Auto-generated method stub
             super .work();
             // 管理 vp
      }

}

当第一次加载CEO类的时候并不会初始化mCeo,只有在第一次调用CEO的getInstance方法的时候mCeo才会被初始化。因此,第一次调用getInstance方法会导致虚拟机加载SinletonCEO类,这种方式不仅能够确保线程安全,同时也能够保证单例对象的唯一性,同时也延迟了单例的实例化。所以这是一种推荐使用的单例实现方式。

(5)其他单例实现方式
其中还有两种单例的实现方式,他们是枚举单例和使用容器实现单例模式。这里就不再具体介绍了,想了解的同学可以翻阅《Android源码设计模式解析与实战》艺术。

6 总结
不管是哪种方式实现的单例模式,它们的核心就是构造函数的私有化,并且通过静态方法来获取一个唯一的实例,在这个过程中我们必须要保证线程安全、防止反序列化导致重生成实例等问题!

    原文作者:grr1314
    原文地址: https://www.jianshu.com/p/f8bf51a80a53
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞