设计模式学习笔记------单例模式

             单例模式 

一、代码实例

1、恶汉单例模式

 1 /**
 2  * 恶汉单例模式
 3  * @author abc
 4  *
 5  */
 6 public class Singleton {
 7     /**
 8      * 定义一个静态变量存储创建好的实例
 9      */
10     private static Singleton singleton = new Singleton();
11     
12     /**
13      * 私有化构造函数,不让外部构造对象实例
14      */
15     private Singleton(){}
16     
17     /**
18      * 提供外部获取示例的方法
19      * @return
20      */
21     public static Singleton getInstance() {
22         return singleton;
23     }
24 }

2、懒汉单例模式

 1 /**
 2  * 懒汉单例模式
 3  * @author abc
 4  *
 5  */
 6 public class Singleton {
 7     /**
 8      * 定义一个静态变量用来存储单例对象
 9      */
10     private static Singleton singleton = null;
11     
12     /**
13      * 私有化构造函数,不让外部构造对象实例
14      */
15     private Singleton(){}
16     
17     /**
18      * 提供外部获取示例的方法
19      * @return
20      */
21     public static Singleton getInstance() {
22         if (singleton == null) {//判断静态变量是否有值
23             singleton = new Singleton();//没有值,则创建实例
24         }
25         return singleton;//有值,则直接返回
26     }
27 }

 

二、两种单例模式特点

  1.恶汉单例模式

    空间换时间:  不管用户使不使用,都先创建对象实例。---节约时间,浪费空间,有可能用户永远都不会使用。创建对象是比较着急,饿了嘛很着急,因此得名恶汉单例模式。

  2.懒汉单例模式

    时间换空间:  到用户需要时,在创建对象的示例。---节约空间,浪费时间,每次调用 getInstance() 时都需要判断,浪费时间,但是可以实现一种懒加载,只在需要使用时,才创建爱。因此得名为懒汉单例模式。

三、线程安全问题

  懒汉单例模式存在线程安全问题,当没有创建Singleton实例时,有多个线程同事调用 getInstance() 这是 singleton都为空,会导致Singleton创建多次。

  解决线程安全

 1 /**
 2  * 懒汉单例模式-线程安全
 3  * @author abc
 4  *
 5  */
 6 public class Singleton {
 7     
 8     /**
 9      * 对保存实例的变量添加volatile的修饰
10      */
11     private volatile static Singleton instance = null;
12     
13     /**
14      * 私有化构造方法
15      */
16     private Singleton() {
17         
18     }
19     
20     public static Singleton getInstance() {
21         //先检查实例是否存在,如果不存在才进入下面的同步快
22         if (instance == null) {
23             //同步快,线程安全的创建实例
24             synchronized(Singleton.class) {
25                 //再次检查实例是否存在,如果不存在才真正的创建实例
26                 if (instance == null) {
27                     instance = new Singleton();
28                 }
29             }
30         }
31         
32         return instance;
33     }
34 }

 先判断实例是否存在,再使用同步代码块,可以节约时间,只用在第一次调用实例是,进行同步操作,在创建完实例后,就只需判断实例是否创建,无需考虑同步。从而加快了运行的速度。

四、在java中一种更好的单例实现方式

  常见的两种单例模式实现方都存在小小的缺陷,那么有没有一种方案,既能实现延迟加载,又能够实现线程安全呢?

  Lazy initialization holder class模式,这个模式综合使用了Java的类级内部类和多线程缺省同步锁的知识,很巧妙的同事实现了延迟加载和线程安全。

  1.相应的基础知识

    先简单地看看类级内部类相关的知识

      a.什么是类级内部类?

      简单点说,类级内部类指的是,有static修饰的成员内部类。如果没有static修饰的成员内部类被称为对象级内部类。

      b.类级内部类相当于其外部类的static成分,他的对象,与外部类对象间不存在依赖关系,因此可以直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。

      c.类级内部类中,可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。

      d.类级内部类相当于其外部类的成员,只有在第一次被使用时才会被装载。

    再来看看多线程缺省同步锁的知识

    大家都知道,在多线程开发中,为了解决并发问题,主要是通过使用 synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地为您执行了同步,这些情况下就不用自己来进行同步控制了。这些情况包括:

      a.有静态初始化器(在静态字段上或 static{} 快中的初始化器)初始化数据时

      b.访问final字段时

      c.在创建线程之前创建对象是

      d.线程可以看见它将要处理的对象时

  2.解决方案的思路

    想要很简单的实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程的安全性。比如前面的恶汉式实现方式。但是这样一来,不是会浪费一定的空间吗?

  因为这种实现方式,会在类装载的时候就初始化对象,不管你需不需要。

    如果现在有一种方法能够让类装载的时候就初始化对象,那不就解决问题了?一种可行的方式就是采用内级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类,那就不会创建对象的实例,从而同事实现延迟加载和线程安全。

  代码示例:

 1 public class Singleton {
 2     
 3     /**
 4      * 类级内部类,就业是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系
 5      * 而且只有被调用到时才会装载,从而实现了延迟加载
 6      * @author abc
 7      *
 8      */
 9     private static class SingletonHolder {
10         /**
11          * 静态初始化器,由JVM来保证线程安全
12          */
13         private static Singleton instance = new Singleton();
14     }
15     
16     /**
17      * 私有化构造方法
18      */
19     private Singleton() {}
20     
21     /**
22      * 获取方法
23      * @return
24      */
25     private static Singleton getInstance() {
26         return SingletonHolder.instance;
27     }
28     
29 }

  当 getInstance 方法第一次被调用的时候,他第一次读取 SingletonHolder.instance, 大致SingletonHolder 类得到初始化,而这个类在装载并被初始化的时候,会初始化的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证他的线程安全性。

  这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。 

五、枚举类实现单例

  枚举类相关知识

    a.Java的枚举类实质上是功能齐全的类,因此可以由自己的属性和方法  

    b.Java枚举类型的基本思想是通过公有的静态final域为每个枚举常亮导出实例的类。

    c.从某个角度讲,枚举是单例的泛型化,本质上是单元素的枚举。

 1 /**
 2  * 使用枚举来实现单例模式
 3  */
 4 public enum Singleton {
 5     /**
 6      * 定义一个枚举类的元素,它就代表了Singleton的一个实例
 7      */
 8     instance;
 9     
10     /**
11      * 示意方法,单例可以有自己的操作
12      */
13     
14     public void singletonOperation() {
15         //处理功能
16     }
17 }

使用枚举类来实现单例控制会更加简洁,而且无偿的提供了序列化的机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。

六、控制单例模式示例数据

 1 /**
 2  * 简单演示如何扩张单例模式,控制实例数目为3个
 3  * @author abc
 4  *
 5  */
 6 public class OneExtend {
 7     /**
 8      * 定义一个缺省的key值的前缀
 9      */
10     private final static String DEFAULT_PREKEY = "Cache";
11     
12     /**
13      * 缓存实例的容器
14      */
15     private static Map<String, OneExtend> map = new HashMap<String, OneExtend>();
16     
17     /**
18      * 用来记录单钱正在说那个的第几个实例,到了控制的最大数目,就返回从1开始
19      */
20     private static int num = 1;
21     
22     /**
23      * 定义控制实例的最大数目
24      */
25     private final static int NUM_MAX = 3;
26     
27     private OneExtend() {}
28     
29     public static OneExtend getInstance() {
30         String key  = DEFAULT_PREKEY + num;
31         OneExtend oneExtend = map.get(key);
32         if (oneExtend == null) {
33             oneExtend = new OneExtend();
34             map.put(key, oneExtend);
35         }
36         num++;//当前实例的序号加1
37         if (num > NUM_MAX) {
38             //如果实例的序号已经达到最大数目了,那就重复从1开始获取
39             num = 1;
40         }
41         
42         return oneExtend;
43     }
44     
45     public static void main(String[] args) {
46         OneExtend t1 = OneExtend.getInstance();
47         OneExtend t2 = OneExtend.getInstance();
48         OneExtend t3 = OneExtend.getInstance();
49         OneExtend t4 = OneExtend.getInstance();
50         OneExtend t5 = OneExtend.getInstance();
51         OneExtend t6 = OneExtend.getInstance();
52             
53         System.out.println("t1==" + t1);
54         System.out.println("t2==" + t2);
55         System.out.println("t3==" + t3);
56         System.out.println("t4==" + t4);
57         System.out.println("t5==" + t5);
58         System.out.println("t6==" + t6);
59     }        
60 }
61 
62 控制台展示
63 t1==cn.itcast.demo.OneExtend@15db9742
64 t2==cn.itcast.demo.OneExtend@6d06d69c
65 t3==cn.itcast.demo.OneExtend@7852e922
66 t4==cn.itcast.demo.OneExtend@15db9742
67 t5==cn.itcast.demo.OneExtend@6d06d69c
68 t6==cn.itcast.demo.OneExtend@7852e922

 

posted @ 2018-01-08 11:57  有悟还有迷  阅读(151)  评论(0编辑  收藏  举报