设计模式-单例模式
单例模式
概念
确保一个类只有一个实例,并提供一个全局访问点。
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; } }
案例
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 }
需求:公司担心同时有两个或两个以上的锅炉实例存在,可能会发生很糟糕的事,要求锅炉只能有一个实例
代码实现
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 }
问题:在用多线程改进代码时,发现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 }
急切创建实例实现
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 }
双重检查加锁实现
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 }
应用场景
对于一些对象,我们只需要一个即可,比如,线程池、对话框、日志对象等,这类对象只能有一个实例,
如果制造多个实例,会产生许多问题,比如,程序的行为异常,资源使用过量或是结果不一致
比如,注册表设置的对象,你不希望这样的对象有多分拷贝吧,这样会把设置搞得一塌糊涂,
单例模式可以确保程序中使用的全局资源只有一份。