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

  在一个软件系统中,经常有有些特殊的对象就需要一个实例,如果有多个的话,就比较浪费服务器资源,最典型的就是 整个系统的配置文件对象。

       普通方式读取配置文件

// 配置文件 SingletonApp.properies
 
paramA2 = AAAAAA
paramB2 =BBBBBBBBBBB


/***************   AppConfig   **************************************/

package designPattern_1_Singleton;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class AppConfig { 
    private String ParamA;
    private String ParamB;

    public String getParamA() {
        return ParamA;
    }

    public String getParamB() {
        return ParamB;
    }
    
    public AppConfig()
    {
        readConfig();
    }
    
    private void readConfig()
    {
        Properties prop = new Properties();
        InputStream in = null;
        try{
            in = this.getClass().getResourceAsStream("SingletonApp.properties");
            
            prop.load(in);
            this.ParamA = prop.getProperty("paramA2");
            this.ParamB = prop.getProperty("paramB2");
            
        }
        catch(IOException e){
            System.out.println("加载配置文件出错了,详细信息参考以下");
            e.printStackTrace();
        }
        finally{
            try { 
                in.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace(); 
            };
        }
    }

}



/********************  Client.java   *****************************/
package designPattern_1_Singleton;

public class Client {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        AppConfig app = new AppConfig();

        String paramA = app.getParamA();
        String paramB = app.getParamB();

        System.out.println("paramA = " + paramA + " paramB = " + paramB);

    }

}
不用单例模式实现

        上面是不用单例模式实现的功能。那么这段代码有什么问题吗?在实现功能方面来说,是完成了功能,但是设计不够合理,这样做没用一次,就要 new 一个config 类。而且发现所有的 Config 类内容都是一样的。这样的话,就存在浪费资源的,这样就有了优化的方面。

        优化的内容就是所有需要用到配置的内容都公用一个 Config 类,这样的话就大大节省了资源。从这个功能的描述我们再抽象一下,就引出了单例模式,下面我们来看一下单例模式的定义:

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

        这样就要保证在整个系统中保证需要用单例模式的类的构造函数只能执行一次。根据这个思路把上面的例子用单例模式改造一下。

   

  1 package designPattern_1_Singleton;
  2 
  3 import java.io.IOException;
  4 import java.io.InputStream;
  5 import java.util.Properties;
  6 
  7 public class SingletonAppConfig {
  8     
  9     /**
 10      * 定义一个静态变量,来存储创建的类的实例,因为是静态的,所以只能创建一次。
 11      */
 12     private static SingletonAppConfig instance = new SingletonAppConfig();
 13     
 14     /**
 15      * 用来提供外界实例类的函数,因为构造函数私有了,那么返回存放 实例的变量的方式来床创建类。
 16      * @return SingletonAppConfig 的实例
 17      */
 18     public static SingletonAppConfig getInstance(){
 19         return instance;
 20     }
 21     
 22     //为了只有一个实例,把构造函数改成私有
 23     private SingletonAppConfig()
 24     {
 25         readConfig();
 26     }
 27     
 28     private String ParamA;
 29     private String ParamB;
 30 
 31     public String getParamA() {
 32         return ParamA;
 33     }
 34 
 35     public String getParamB() {
 36         return ParamB;
 37     }
 38     
 39     private void readConfig()
 40     {
 41         Properties prop = new Properties();
 42         InputStream in = null;
 43         try{
 44             in = this.getClass().getResourceAsStream("SingletonApp.properties");
 45             
 46             prop.load(in);
 47             this.ParamA = prop.getProperty("paramA2");
 48             this.ParamB = prop.getProperty("paramB2");
 49             
 50         }
 51         catch(IOException e){
 52             System.out.println("加载配置文件出错了,详细信息参考以下");
 53             e.printStackTrace();
 54         }
 55         finally{
 56             try { 
 57                 in.close();
 58             } catch (IOException e) {
 59                 // TODO Auto-generated catch block
 60                 e.printStackTrace(); 
 61             };
 62         }
 63     }
 64 }
 65 
 66 
 67 
 68 *****************************************************************
 69 
 70 
 71 package designPattern_1_Singleton;
 72 
 73 public class Client2 {
 74 
 75     public static void main(String[] args) {
 76         // TODO Auto-generated method stub
 77         SingletonAppConfig sConfig = SingletonAppConfig.getInstance();
 78         for(int i=0;i<=300;i++)
 79         {
 80             sConfig = SingletonAppConfig.getInstance();
 81             System.out.println(sConfig);
 82         }
 83         System.out.println(sConfig.getParamA()+" **** "+sConfig.getParamB());
 84     }
 85 }
 86 
 87 **************************************************************
 88 designPattern_1_Singleton.SingletonAppConfig@4e8a88a9
 89 designPattern_1_Singleton.SingletonAppConfig@4e8a88a9
 90 designPattern_1_Singleton.SingletonAppConfig@4e8a88a9
 91 designPattern_1_Singleton.SingletonAppConfig@4e8a88a9
 92 designPattern_1_Singleton.SingletonAppConfig@4e8a88a9
 93 designPattern_1_Singleton.SingletonAppConfig@4e8a88a9
 94 designPattern_1_Singleton.SingletonAppConfig@4e8a88a9
 95 designPattern_1_Singleton.SingletonAppConfig@4e8a88a9
 96 designPattern_1_Singleton.SingletonAppConfig@4e8a88a9
 97 designPattern_1_Singleton.SingletonAppConfig@4e8a88a9
 98 designPattern_1_Singleton.SingletonAppConfig@4e8a88a9
 99 designPattern_1_Singleton.SingletonAppConfig@4e8a88a9
100 designPattern_1_Singleton.SingletonAppConfig@4e8a88a9
101 designPattern_1_Singleton.SingletonAppConfig@4e8a88a9
102 AAAAAA **** BBBBBBBBBBB
Singleton_1

 

  通过以上代理,也实现了读取配置文件的功能,但是明显修改后性能更好,因为修改后,这个类在系统中无论在什么地方调用,都只能实现一个实例,能够节省很多性能,改造后的就是单例模式。

  单例模式整体上有两种实现方式,一种就是上面的这种,饿汉模式,另外一种是懒汉模式。

  那么怎么区分饿汉模式和懒汉模式呢,上面实现的是饿汉模式,下面贴出来懒汉模式,然后在区分下:

 1 package designPattern_1_Singleton;
 2 
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 import java.util.Properties;
 6 
 7 /*
 8  * 懒汉式实现办法
 9  */
10 public class Singleton2AppConfig {
11 
12     private static Singleton2AppConfig s2Config = null;
13     
14     public Singleton2AppConfig getInstance(){
15         //判断是否创建了实例,如果没有则创建
16         if(s2Config == null){
17             s2Config = new Singleton2AppConfig();
18         }
19         return s2Config;
20     }
21     
22     private Singleton2AppConfig(){
23         readConfig();
24     }
25     
26     private String ParamA;
27     private String ParamB;
28 
29     public String getParamA() {
30         return ParamA;
31     }
32 
33     public String getParamB() {
34         return ParamB;
35     }
36     
37     private void readConfig()
38     {
39         Properties prop = new Properties();
40         InputStream in = null;
41         try{
42             in = this.getClass().getResourceAsStream("SingletonApp.properties");
43             
44             prop.load(in);
45             this.ParamA = prop.getProperty("paramA2");
46             this.ParamB = prop.getProperty("paramB2");
47             
48         }
49         catch(IOException e){
50             System.out.println("加载配置文件出错了,详细信息参考以下");
51             e.printStackTrace();
52         }
53         finally{
54             try { 
55                 in.close();
56             } catch (IOException e) {
57                 // TODO Auto-generated catch block
58                 e.printStackTrace(); 
59             };
60         }
61     }
62 }
Singleton_2

   通过 Singleton_1 与 Singleton_2 比较,两个模式的本质区别是:创建对象实例的先后。

   饿汉式单例模式,形象的说就是很饿,要急吼吼的创建实例,急到加载类的时候就创建了实例对象。关键代码是

1 /**
2  * 定义一个静态变量,来存储创建的类的实例,因为是静态的,所以只能创建一次。
3 */
4 private static SingletonAppConfig instance = new SingletonAppConfig();

       懒汉式单例模式,很实例的创建拖拖拉拉,能不创建就不创建。直到需要使用的时候再去进行了创建,关键代码是:

1 private static Singleton2AppConfig s2Config = null;
2     
3 public static Singleton2AppConfig getInstance(){
4     //判断是否创建了实例,如果没有则创建
5     if(s2Config == null){
6         s2Config = new Singleton2AppConfig();
7     }
8     return s2Config;
9 }

     通过以上要点总结下单例模式的要点:

   1、因为类的性质,他所有实例都是相同的,因此控制这个类只能有一个实例对象。

   2、定义一个私有静态变量,存放实例后的类,因为是静态变量。

   3、类的改造函数设置为私有,不让外界通过构造函数创建实例对象。

   4、但是使用类总是要实例的,所以就提供一个共有的静态方法来创建实例对象。但是为了让类能够使用这个方法创建对象,所以这个类必须要是静态方法。

        5、通过提供的静态方法(一般都是 getInstance)来创建类实例(因为普通方法需要先实例后才能调用,所以这个方法必须是静态方法)。

  懒汉式和饿汉式模式区别

         1、从时间和空间方面来说,懒汉式是因为用的时候才创建实例,而且在每次创建类的时候都需要判断,所以是时间空间模式。而饿汉式模式是每次装载类的时候就要进行实例,因此是空间换时间。

         2、从线程安全方面来讲。饿汉式因为是加载类的时候就实例化了,而虚拟机保证加载类只会加载一次,因此是线程安全的。但是装载类和实例类不同步的懒汉式则是不安全的。

         

 1 private static Singleton2AppConfig s2Config = null;
 2     
 3 public static Singleton2AppConfig getInstance(){
 4     //判断是否创建了实例,如果没有则创建
 5     if(s2Config == null){
 6         s2Config = new Singleton2AppConfig();
 7     }
 8         
 9     return s2Config;
10 }

 

      这个问题就是要用加锁的方式来解决

     

 1 /**
 2   * volatile 修改符保证这个类每次都是执行 volatile 
 3   */
 4 private volatile static Singleton2AppConfig s2Config = null;
 5     
 6 public static Singleton2AppConfig getInstance(){
 7     //判断是否创建了实例,如果没有则创建
 8     if(s2Config == null){
 9         synchronized (Singleton2AppConfig.class) {
10             s2Config = new Singleton2AppConfig();
11         }
12     }
13         
14     return s2Config;
15 }

  加锁后由于只是在第一次创建类使用同步锁功能,因此影响也不大。

  实例模式的变种,可能创建指定数量的实例

           单例模式的本质是控制类实例化的数量,那么如果单例的这个类如果使用频率高到一个实例不够使用了,那么是否可以达到指定需要的实例数量呢?这个是可以的。实例化有一定的数量后,调用肯定也是有特殊的算法,这里我们不探究调用的算法,只看如果实现指定数量的类实例。看代码:

 1 package designPattern_1_Singleton;
 2 
 3 import java.util.HashMap;
 4 import java.util.Map;
 5 
 6 public class Singleton3AppConfig {
 7     private final static String CACHE = "cache";
 8 
 9     private static Map<String, Singleton3AppConfig> map = new HashMap<String, Singleton3AppConfig>();
10 
11     private static int MAX = 3;
12 
13     private static int num = 1;
14 
15     private Singleton3AppConfig() {
16         
17     }
18 
19     public static Singleton3AppConfig getInstance() {
20         String classkey = CACHE + num;
21         if (map.get(classkey) == null) {
22             
23             map.put(classkey, new Singleton3AppConfig());
24         }
25         num += 1;
26         if (num > MAX) {
27             num = 1;
28         }
29 
30         return map.get(classkey);
31     }
32 }
33 
34 
35 
36 /***********************8*************************************/
37 
38 package designPattern_1_Singleton;
39 
40 public class Client3 {
41 
42     public static void main(String[] args) {
43         // TODO Auto-generated method stub
44         for(int i=0;i<30;i++){
45             Singleton3AppConfig s3c = Singleton3AppConfig.getInstance();
46             System.out.println(s3c);
47         }
48         
49     }
50 
51 }
52 
53 /***********************8*************************************/
54 
55 designPattern_1_Singleton.Singleton3AppConfig@5f4fcc96
56 designPattern_1_Singleton.Singleton3AppConfig@7000bcbc
57 designPattern_1_Singleton.Singleton3AppConfig@40671416
58 designPattern_1_Singleton.Singleton3AppConfig@5f4fcc96
59 designPattern_1_Singleton.Singleton3AppConfig@7000bcbc
60 designPattern_1_Singleton.Singleton3AppConfig@40671416
61 designPattern_1_Singleton.Singleton3AppConfig@5f4fcc96
62 designPattern_1_Singleton.Singleton3AppConfig@7000bcbc
63 designPattern_1_Singleton.Singleton3AppConfig@40671416
64 designPattern_1_Singleton.Singleton3AppConfig@5f4fcc96
65 designPattern_1_Singleton.Singleton3AppConfig@7000bcbc
66 designPattern_1_Singleton.Singleton3AppConfig@40671416
67 designPattern_1_Singleton.Singleton3AppConfig@5f4fcc96
68 designPattern_1_Singleton.Singleton3AppConfig@7000bcbc
69 designPattern_1_Singleton.Singleton3AppConfig@40671416
70 designPattern_1_Singleton.Singleton3AppConfig@5f4fcc96
71 designPattern_1_Singleton.Singleton3AppConfig@7000bcbc
72 designPattern_1_Singleton.Singleton3AppConfig@40671416
73 designPattern_1_Singleton.Singleton3AppConfig@5f4fcc96
74 designPattern_1_Singleton.Singleton3AppConfig@7000bcbc
75 designPattern_1_Singleton.Singleton3AppConfig@40671416
76 designPattern_1_Singleton.Singleton3AppConfig@5f4fcc96
77 designPattern_1_Singleton.Singleton3AppConfig@7000bcbc
78 designPattern_1_Singleton.Singleton3AppConfig@40671416
79 designPattern_1_Singleton.Singleton3AppConfig@5f4fcc96
80 designPattern_1_Singleton.Singleton3AppConfig@7000bcbc
81 designPattern_1_Singleton.Singleton3AppConfig@40671416
82 designPattern_1_Singleton.Singleton3AppConfig@5f4fcc96
83 designPattern_1_Singleton.Singleton3AppConfig@7000bcbc
84 designPattern_1_Singleton.Singleton3AppConfig@40671416

 这个是单例模式变种,代码很好理解。这儿看执行效果,虽然调用的地方实例了30个,但是从打印内容来看,只有三个 实例化的地址。所以知识实例化了指定的三个实例。(这里补充一点:如果构造函数没有返回一个字符串,那么如果直接输出类的话,就输出的是他的地址

    小结

   单例模式是整个设计模式中最好理解,也是在使用场景中最容易分辨使用的设计模式。使用单例模式不会因为使用设计模式让代码变得较复杂。而他的目的就是为了在让合适的类只有一个实例对象,从而达到节省资源开支的目的。

posted @ 2019-03-19 22:13  彭卫强  阅读(481)  评论(0编辑  收藏  举报
本文原创,文中若有表述不清或存在问题,欢迎指正。共同学习