java设计模式之单例模式(Singleton)

   利用元旦小假期,参考了几篇单例模式介绍的文章,而后本身下午对java设计模式中的单例模式作了一下简单的总结,主要是代码介绍。java

      单例模式,在实际项目开发中运用普遍,好比数据库链接池,实际上,配置信息类、管理类、控制类、门面类、代理类一般被设计为单例类。像Java的Struts、spring框架,.Net的Spring.Net框架,以及PHP的Zend框架都大量使用单例模式。spring

  那么,接下来,我将如下面5点来对单例模式做一下介绍:数据库

  1.单例模式的定义设计模式

  2.单例模式的特色安全

  3.为何要使用单例模式?网络

  4.单例模式的5种不一样写法及其总结多线程

  5.拓展--如何防止Java反射机制对单例类的攻击?并发

1.单例模式的定义

    单例模式(Singleton)是一种建立型模式,指某个类采用Singleton模式,则在这个类被建立后,只可能产生一个实例供外部访问,而且提供一个全局的访问点。框架

核心知识点:
 (1)将采用单例模式的类的构造方法私有化(采用private修饰);
 (2)在其内部产生该类的实例化对象,并将其封装成private static类型
 (3)定义一个静态方法返回该类的实例。
函数

2.单例模式的特色

  - 单例类只能有一个实例;
  - 单例类必须本身建立本身的惟一实例;
  - 单例类必须给全部其余对象提供这一实例。

3.为何要使用单例模式?

  根据单例模式的定义和特色,咱们会对单例模式有了初步认识,那么由特色出发,单例模式在项目中的做用就显而易见了。

  (1)控制资源的使用,经过线程同步来控制资源的并发访问
  (2)控制实例产生的数量,到达节约资源的目的;
  (3)做为通讯媒介使用,也就是数据共享,他能够在不创建直接关联的条件下,让多个不相关的两个线程或者进程实现通讯。
  好比:
  数据库链接池的设计通常采用单例模式,数据库链接是一种数据资源。项目中使用数据库链接池,主要是节省打开或者关闭数据库所引发的效率损耗。固然,使用数据库链接池能够屏蔽不一样数据数据库之间的差别,实现系统对数据库的低度耦合,也能够被多个系统同时使用,具备高科复用性,还能方便对数据库链接的管理等。
  实际上,配置信息类、管理类、控制类、门面类、代理类一般被设计为单例类。像Java的Struts、spring框架,.Net的Spring.Net框架,以及PHP的Zend框架都大量使用单例模式。

4.单例模式的5种不一样写法及其总结

  单例模式的实现经常使用的有5种,分别是:

  (1).饿汉式;

  (2).懒汉式(、加同步锁的懒汉式、加双重校验锁的懒汉式、防止指令重排优化的懒汉式);

  (3).登记式单例模式;

  (4)静态内部类单例模式;

  (5).枚举类型的单例模式。

  接下来,我就以代码为主来对各类实现方式介绍一下。

项目工程结构:如图中的红框1中所示。

 

(1).饿汉式

代码清单【1】

 1 package com.lxf.singleton;
 2 
 3 /**
 4 
 5  * 单例类--饿汉模式   线程安全
 6  * @author Administrator
 7  * 
 8  */
 9 public class Singleton 
10 {
11     private static final Singleton INSTANCE = new Singleton();
12     
13     private static boolean flag = true;
14     private Singleton()
15     {
16     }
17     
18     public static Singleton newInstance()
19     {
20         return INSTANCE;
21     }
22 
23 }

  从代码中,咱们能够看到,该类的构造函数被定义为private,这样就保证了其余类不能实例化此类,而后该单例类提供了一个静态实例并返回给调用者(向外界提供了调用该类方法的实例)。饿汉模式在类加载的时候就对该实例进行建立,实例在整个程序周期都存在。

  优势:只在类加载的时候建立一次,不会存在多个线程建立多个实例的状况,避免了多线程同步的问题,是线程安全的。
  缺点:在整个程序周期中,即便这个单例没有被用到也会被加载,并且在类加载以后就被建立,内存就被浪费了。
  使用场景:适合单例占用内存比较小,在初始化就被用到的状况。可是,若是单例占用的内存比较大,或者单例只是在某个场景下才会被使用到,使用该模式就不合适了,这时候就要考虑使用“懒汉模式”进行延迟加载。

(2).懒汉式(、加同步锁的懒汉式、加双重校验锁的懒汉式、防止指令重排优化的懒汉式)

2.1--懒汉式(、加同步锁的懒汉式

代码清单【2.1】

 1 package com.lxf.singleton;
 2 
 3 /**
 4  * 懒汉式单例模式   线程不安全
 5  * @author Administrator
 6  *
 7  */
 8 public class Singleton2 
 9 {
10     private static Singleton2 instance = null;
11     
12     private Singleton2(){}
13 
14     /*
15      * 1.未加同步锁
16      */
17     /*
18     public static Singleton2 getInstance()
19     {
20         if(instance == null)
21         {
22             instance = new Singleton2();
23         }
24         return instance;
25     }
26     */
27     
28     /*
29      * 2.加同步锁     线程安全
30      * 上面的懒汉模式并无考虑多线程的安全问题,在多性格线程可能并发调用它的getInsatance()方法,
31      * 致使建立多个实例,所以须要加锁来解决线程同步问题。
32      */
33     public static synchronized Singleton2 getInstance()
34     {
35         if(instance == null)
36         {
37             instance = new Singleton2();
38         }
39         return instance;
40     }
41
46 }

  懒汉式单例模式是在须要的时候才去建立,若是调用该接口获取实例的时候,发现该实例不存在,就会被建立;若是发现该实例已经存在,就会返回以前已经建立出来的实例。

  可是懒汉模式的单例设计,是线程不安全的,没有考虑线程安全问题。若是你的程序是多线程的,而这些线程可能会同时运行这段代码。若是每次运行结果和单线程的运行结果同样的,并且其余的变量的值也和预期同样的,就是线程安全的。显然,懒汉式单例模式并非线程安全的,在多线程并发环境下,可能会建立出来多个实例。

  使用场景:适合在项目中使用单例类数量较少,并且占用资源比较多的项目,能够考虑使用懒汉式单例模式。

2.2--加双重校验锁的懒汉式、防止指令重排优化的懒汉式

 

代码清单【2.2】

 

 1 package com.lxf.singleton;
 2 
 3 /**
 4  * 双重校验锁     线程安全
 5  * @author Administrator
 6  * 
 7  */
 8 public class Singleton3
 9 {
10     private static Singleton3 instance = null;
11     //禁止指令重排优化
12     //private static volatile Singleton3 instance = null;
13     private Singleton3(){}
14     
15     public static Singleton3 getInstance()
16     {
17         if(null == instance)
18         {
19             synchronized (Singleton3.class)
20             {
21                 if(null == instance)
22                 {
23                     //双重校验
24                     instance = new Singleton3();
25                 }
26                 
27             }
28         }
29         return instance;
30     }
31 
32 }

 

 

 

   在加锁的懒汉模式中,看似解决了线程的并发安全问题,有实现了延迟加载,然而它存在着性能问题。synchronized修饰的同步方法比通常方法要慢不少,若是屡次调用getInstance(),累积的性能损耗就比较大了。所以,咱们这里就有了双重校验锁。在上面的双重校验锁代码中,因为单例对象只须要建立一次,若是后面再次调用getInstance()只须要直接返回单例对象。

     所以,大部分状况下,调用getInstance()都不会执行到同步代码块中的代码,从而提升了性能

     不过,在这里要提到Java中的指令重排优化。指令重排优化:在不改变原语义的状况下,经过调整指令的执行顺序让程序运行的更快。

     因为指令重拍优化的存在,致使初始化Singleton3和将对象地址付给instance字段的顺序是不肯定的。好比:在某个线程建立单例对象时,在构造方法被调用以前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就能够将分配的内存地址复制给instance字段了,而后该对象可能尚未初始化。若紧接着另一个线程来调用getInstance,获得的是状态不肯定的对象,程序就会出错。

  以上就是双重校验锁会失效的缘由。不过在JDK1.5及其之后的版本中,增长了volatile关键字。volatile的关键字的一个语义就是禁止指令重排优化,这样就保证了instance变量被赋值的时候已是初始化的,避免了上面提到的状态不肯定的问题。

3.登记式单例模式

代码清单【3】

 1 package com.lxf.singleton;
 2 
 3 import java.util.HashMap;
 4 import java.util.Map;
 5 
 6 /**
 7  * 登记式单例模式   线程安全
 8  * @author Administrator
 9  *就是将该类名进行登记,每次调用前查询,若是存在,则直接使用;不存在,则进行登记。
10  *这里使用Map<String,Class>
11  */
12 public class Singleton4 
13 {
14     private static Map<String, Singleton4> map = new HashMap<String, Singleton4>();
15     
16     /*
17      * 静态语句块,保证其中的内容在类加载的时候运行一次,而且只运行一次。
18      */
19     static 
20     {
21         Singleton4 singleton4 = new Singleton4();
22         map.put(singleton4.getClass().getName(), singleton4);
23     }
24     
25     //保护的默认构造子
26     protected Singleton4 (){}
27     //静态工厂方法,返回此类惟一的实例
28     public static Singleton4 getInstance(String name)
29     {
30         if(null == name)
31         {
32             name = Singleton4.class.getName();
33             System.out.println("name == null  --- > name == " + name);
34         }
35         if(null == map.get(name))
36         {
37             try {
38                 map.put(name, (Singleton4) Class.forName(name).newInstance());
39             } catch (InstantiationException e) {
40                 e.printStackTrace();
41             } catch (IllegalAccessException e) {
42                 e.printStackTrace();
43             } catch (ClassNotFoundException e) {
44                 e.printStackTrace();
45             }
46         }
47         return map.get(name);
48     }
49     
50 }

 

 4.静态内部类单例模式;

代码清单【4】

 1 package com.lxf.singleton;
 2 /**
 3  * 静态内部类单例模式  线程安全
 4  * @author Administrator
 5  */
 6 public class Singleton5 
 7 {
 8     /*
 9      * 内部类,用于实现延迟机制
10      * @author Administrator
11      */
12     private static class SingletonHolder
13     {
14         private static Singleton5 instance = new Singleton5();
15     }
16     //私有的构造方法,保证外部的类不能经过构造器来实例化
17     private Singleton5(){}
18     
19     /*
20      *获取单例对象的实例
21      */
22     public static Singleton5 getInstacne()
23     {
24         return SingletonHolder.instance;
25     }
26 
27 }

  这种方式一样利用了类加载机制来保证只建立一个insatcne实例。所以不存在多线程并发的问题。它是在内部类里面去建立对象实例,这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类
也就不会加载单例对象,从而实现了延迟加载。

 

5.枚举类型的单例模式。

代码清单【5】

package com.lxf.singleton;

/**
 * 咱们要建立的单例类资源,好比:数据库链接,网络链接,线程池之类的。   
 * @author Administrator
 *
 */
class Resource
{
    public void doMethod()
    {
        System.out.println("枚举类型的单例类资源");
    }
}

/**
 * 枚举类型的单例模式   线程安全
 * 
 * 获取资源的方式,Singleton6.INSTANCE.getInstance();便可得到所要的实例。
 * @author Administrator
 *
 */


public enum Singleton6
{
    INSTANCE;
    
    private Resource instance;
    
    Singleton6()
    {
        instance = new Resource();
    }
    
    public Resource getInstance()
    {
        return instance;
    }
    
    
}

  上面代码中,首先,在枚举中咱们明确了构造方法限制为私有,在咱们访问枚举实例时会执行构造方法,同时每一个枚举实例都是static final类型的,也就代表只能被实例化一次。在调用构造方法时,咱们的单例被实例化。也就是说,由于enum中的实例被保证只会被实例化一次,因此咱们的INSTANCE也就会被实例化一次。
  在以前介绍的实现单例的方式中都有共同的缺点:
(1).须要额外的工做来实现序列化,不然每次反序列化一个序列化的对象时都会建立一个新的实例;
(2).可使用反射强行调用私有构造器(若是要避免这个状况,能够修改构造器,让它在建立第二我的实例的时候抛异常。)这个会在第5点中进行介绍
  而使用枚举出了线程安全和防止反射调用构造器以外,还提供了自动序列化机制,防止反序列化的时候建立新的对象。

6.单例模式测试类   SingletonMain.java

 代码清单【6】

  1 package com.lxf.singleton;
  2 
  3 import org.junit.Test;
  4 
  5 public class SingletonMain 
  6 {
  7     /**
  8      *1. 饿汉模式单例测试
  9      */
 10     @Test
 11     public void testSingletonTest()
 12     {
 13         System.out.println("-------饿汉模式单例测试--------------");
 14         Singleton singleton = Singleton.newInstance();
 15         //singleton.about();
 16         Singleton singleton2 = Singleton.newInstance();
 17         //singleton2.about();
 18         
 19         if(singleton == singleton2)
 20         {
 21             System.out.println("1.singleton and singleton2 are same Object");
 22         }
 23         else
 24         {
 25             System.out.println("1.singleton and singleton2 aren't same Object");
 26         }
 27         System.out.println("---------------------------------------------");
 28     }
 29     
 30     /**
 31      *2. 懒汉式单例模式测试
 32      */
 33     @Test
 34     public void testSingleton2Test()
 35     {
 36         System.out.println("-------懒汉式单例模式测试--------------");
 37         Singleton2 singleton = Singleton2.getInstance();
 38         Singleton2 singleton2 = Singleton2.getInstance();
 39         
 40         if(singleton == singleton2)
 41         {
 42             System.out.println("2.singleton and singleton2 are same Object");
 43         }
 44         else
 45         {
 46             System.out.println("2.singleton and singleton2 aren't same Object");
 47         }
 48         System.out.println("---------------------------------------------");
 49     }
 50     
 51     /**
 52      * 3.双重校验锁单例模式测试
 53      */
 54     @Test
 55     public void testSingleton3()
 56     {
 57         System.out.println("-------双重校验锁单例模式测试--------------");
 58         Singleton3 singleton = Singleton3.getInstance();
 59         Singleton3 singleton2 = Singleton3.getInstance();
 60         
 61         if(singleton == singleton2)
 62         {
 63             System.out.println("3.singleton and singleton2 are same Object");
 64         }
 65         else
 66         {
 67             System.out.println("3.singleton and singleton2 aren't same Object");
 68         }
 69         System.out.println("---------------------------------------------");
 70     }
 71     
 72     /**
 73      * 4.登记式单例模式测试
 74      */
 75     @Test
 76     public void testSingleton4()
 77     {
 78         System.out.println("-------双重校验锁单例模式测试--------------");
 79         Singleton4 singleton = Singleton4.getInstance(Singleton4.class.getName());
 80         Singleton4 singleton2 = Singleton4.getInstance(Singleton4.class.getName());
 81         if(singleton == singleton2)
 82         {
 83             System.out.println("4.singleton and singleton2 are same Object");
 84         }
 85         else
 86         {
 87             System.out.println("4.singleton and singleton2 aren't same Object");
 88         }
 89         System.out.println("---------------------------------------------");
 90     }
 91     
 92     /**
 93      *5. 静态内部类单例模式测试
 94      */
 95     @Test
 96     public void testSingleton5()
 97     {
 98         System.out.println("-------静态内部类单例模式测试--------------");
 99         Singleton5 singleton = Singleton5.getInstacne();
100         Singleton5 singleton2 = Singleton5.getInstacne();
101         if(singleton == singleton2)
102         {
103             System.out.println("5.singleton and singleton2 are same Object");
104         }
105         else
106         {
107             System.out.println("5.singleton and singleton2 aren't same Object");
108         }
109         System.out.println("---------------------------------------------");
110     }
111     
112     /**
113      *6. 静态内部类单例模式测试
114      */
115     @Test
116     public void testSingleton6()
117     {
118         System.out.println("-------枚举类型的单例类资源测试--------------");
119         Resource singleton = Singleton6.INSTANCE.getInstance();
120         Resource singleton2 = Singleton6.INSTANCE.getInstance();
121         if(singleton == singleton2)
122         {
123             System.out.println("6.singleton and singleton2 are same Object");
124         }
125         else
126         {
127             System.out.println("6.singleton and singleton2 aren't same Object");
128         }
129         System.out.println("---------------------------------------------");
130     }
131     
132 
133 }

 

运行结果:

 

5.拓展--如何防止Java反射机制对单例类的攻击?

  上面介绍的除了最后一种枚举类型单例模式外,其他的写法都是基于一个条件:确保不会被反射机制调用私有的构造器。
那么如何防止Java反射机制对单例类的攻击呢?请参考下一篇随笔:《如何防止反射机制对单例类的攻击?》

 

 6.后期补充

相关文章
相关标签/搜索