设计模式-单例模式

单例模式

概念

确保一个类只有一个实例,并提供一个全局访问点。

UML类图

UML类图说明

1)getInstance()是一个静态方法,它是一个全局访问点,我们可以通过Singleton.getInstance()来访问它,

这和访问全局变量一样简单,只是多了一个优点:单例模式可以延迟实例化

2)类变量uniqueInstance用来指向唯一的类实例

单例模式的实现

分析:

  依靠程序之间的约定或利用全局变量

  比如利用Java的静态变量

        V

  单例模式经过了实践的考验,可确保只有一个实例被创建,

  也给了我们一个全局访问点,优于程序员之间的约定和全局变量,

  并且没有全局变量的缺点

        V

  全局变量的缺点:1)污染命名空间

          2)在程序一开始就要创建对象,如果这个对象非常消耗资源,而程序的这次执行又没用到它,

          就会造成浪费,单例模式可以在需要时创建。

单件模式的实现分析

  一个类如果不是私有的,那么我们可以多次实例化它

            V

  如果把一个类(MyClass)的构造函数私有化,那么只有这个类(MyClass)内的代码才能

  调用此构造器

            V

  必须有MyClass的实例才能调用MyClass构造器,没有其他类能够实例化MyClass,

  这是个鸡生蛋蛋生鸡的问题

            V

  提供一个静态方法,通过类来调用,比如MyClass.getInstance()

            V

  你能够确保MyClass只能产生一个实例吗?答案是否定的

说明

1)我们把某个类设计成自己管理的一个单独实例,同时也避免了其他类再自行产生实例,

想要取得单例实例,通过单例类是唯一的途径

2)我们提供了对这个实例的全局访问点,当你需要实例时,向类查询,它会返回单个实例,

利用延迟实例化的方式创建单例对资源敏感的对象特别重要

单例模式基本实现

Singleton.java

package com.java.singleton;

public class Singleton {
    private static Singleton uniqueInstance = null;
    
    private Singleton(){}
    
    public static Singleton getInstance(){
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}
View Code

案例

ChocOHolic公司有工业强度巧克力锅炉控制器,锅炉负责把巧克力和牛奶融在一起,

然后送到下一个阶段,以制造巧克力棒,代码如下

ChocolateBoiler.java

 1 package com.design.singleton;
 2 
 3 public class ChocolateBoiler {
 4     private boolean empty;
 5     private boolean boiled;
 6     
 7     //初始化时,锅炉室空的
 8     public ChocolateBoiler() {
 9         empty = true;
10         boiled = false;
11     }
12     
13     public boolean isEmpty() {
14         return empty;
15     }
16     
17     public boolean isBoiled() {
18         return boiled;
19     }
20     
21     //往锅炉填入原料时,锅炉必须是空的
22     public void fill() {
23         if (isEmpty()) {
24             empty = false;
25             boiled = false;
26             //填入巧克力和牛奶的混合物
27         }
28     }
29     
30     //锅炉排出时,必须是满的并且是被煮过的
31     public void drain() {
32         if(!isEmpty() && isBoiled()) {
33             //排出煮沸的巧克力和牛奶
34             empty = true;
35         }
36     }
37     
38     //煮混合物时,锅炉必须是满的并且是没有被煮过的
39     public void boil() {
40         if (!isEmpty() && !isBoiled()) {
41             //将锅炉内的混合物煮沸
42             boiled = true;
43         }
44     }
45 }
View Code

需求:公司担心同时有两个或两个以上的锅炉实例存在,可能会发生很糟糕的事,要求锅炉只能有一个实例

代码实现

ChocolateBoiler.java

 1 package com.design.singleton;
 2 
 3 public class ChocolateBoiler {
 4     private boolean empty;
 5     private boolean boiled;
 6     
 7     //初始化时,锅炉室空的
 8 //    public ChocolateBoiler() {
 9 //        empty = true;
10 //        boiled = false;
11 //    }
12     public static ChocolateBoiler chocolateBoiler = null;
13     
14     public static ChocolateBoiler getInstance() {
15         if (chocolateBoiler == null) {
16             chocolateBoiler = new ChocolateBoiler();
17         }
18         return chocolateBoiler;
19     }
20     
21     private ChocolateBoiler() {
22         empty = true;
23         boiled = false;
24     }
25     
26     
27     public boolean isEmpty() {
28         return empty;
29     }
30     
31     public boolean isBoiled() {
32         return boiled;
33     }
34     
35     //往锅炉填入原料时,锅炉必须是空的
36     public void fill() {
37         if (isEmpty()) {
38             empty = false;
39             boiled = false;
40             //填入巧克力和牛奶的混合物
41         }
42     }
43     
44     //锅炉排出时,必须是满的并且是被煮过的
45     public void drain() {
46         if(!isEmpty() && isBoiled()) {
47             //排出煮沸的巧克力和牛奶
48             empty = true;
49         }
50     }
51     
52     //煮混合物时,锅炉必须是满的并且是没有被煮过的
53     public void boil() {
54         if (!isEmpty() && !isBoiled()) {
55             //将锅炉内的混合物煮沸
56             boiled = true;
57         }
58     }
59 }
View Code

问题:在用多线程改进代码时,发现fill()方法竟然允许在加热的过程中继续加入原料

分析

  通过增加synchronized关键字到getInstance()方法中,

  迫使每个线程在进入这个方法之前,要先等候别的线程离开这个方法

               V

  但是同步会降低性能,并且只有第一次执行方法时才真正需要同步,

  之后每次调用这个方法,同步都是一种累赘,同步一个方法可能造成

  程序执行效率下降100

               V

  如果你的程序可以接受getInstance()造成的额外负担,那就忘了这件事吧

               V

  如果getInstance()方法调用比较繁琐,我们可以使用急切的创建实例,

  而不用延迟实例化的做法

               V

  依赖JVM在加载这个类时马上创建此类唯一的实例,JVM保证在任何线程访问唯一静态实变量之前,

  先创建此实例

               V

  利用双重检查加锁,首先检查实例是已经被创建,如果未被创建,才进行同步,

  如果创建了,则不进行同步,这正是我们想要的:只有第一次才会同步。

synchronized实现

 1 package com.design.singleton;
 2 
 3 public class Singleton {
 4     public static Singleton instance = null;
 5     
 6     private Singleton() {}
 7     
 8     public static synchronized Singleton getInstance() {
 9         if (instance == null) {
10             instance = new Singleton();
11         }
12         return instance;
13     }
14 }
View Code

急切创建实例实现

 1 package com.design.singleton;
 2 
 3 public class Singleton {
 4     public static Singleton instance = new Singleton();
 5     
 6     private Singleton() {}
 7     
 8     public static synchronized Singleton getInstance() {
 9         return instance;
10     }
11 }
View Code

双重检查加锁实现

 1 package com.design.singleton;
 2 
 3 public class Singleton {
 4     public volatile static Singleton instance;
 5     
 6     private Singleton() {}
 7     
 8     public static Singleton getInstance() {
 9         if (instance == null) {
10             synchronized (Singleton.class) {
11                 if (instance == null) {
12                     instance = new Singleton();
13                 }
14             }
15         }
16         return instance;
17     }
18 }
View Code

            

应用场景

对于一些对象,我们只需要一个即可,比如,线程池、对话框、日志对象等,这类对象只能有一个实例,

如果制造多个实例,会产生许多问题,比如,程序的行为异常,资源使用过量或是结果不一致

比如,注册表设置的对象,你不希望这样的对象有多分拷贝吧,这样会把设置搞得一塌糊涂,

单例模式可以确保程序中使用的全局资源只有一份。

原文地址:https://www.cnblogs.com/marton/p/11553632.html