今天我们来看一下单件模式,这个模式是所有模式中类图最简单的哦!
为什么用单件模式:
有些对象我们只需要一个,比如:连接池、缓存、对话框、和注册表对象、日志对
象等对象。事实上,这类对象只能有一个实例,如果制造出多个实例,就会导致许
多问题产生,例如:程序的行为异常、资源使用过量,或者是不一致的结果。也就
是为了防止多次 New 对象。
从一个简单的单件模式入门:
1 public class Singleton { 2 private static Singleton uniqueInstance; 3 4 // other useful instance variables here 5 6 private Singleton() {} 7 8 public static Singleton getInstance() { 9 if (uniqueInstance == null) {10 uniqueInstance = new Singleton();11 }12 return uniqueInstance;13 }14 15 // other useful methods here16 public String getDescription() {17 return "I'm a thread safe Singleton!";18 }19 }
在这里主要注意的点有:
第二行:利用一个静态变量来记录Singleton类的唯一实例;
第六行:把构造器声明为私有的,只有字Singleton类内才可以调用构造器;
第八行至第十三行:用getInstance()方法实例化对象并返回这个示例(如果
uniqueInstance是空的则利用私有构造器产生一个Sin-
gleton实例,否则表示已经有了实例,并将uinqueIns-
tance当返回值)。
让我们写一个测试类(Main.java):
1 public class Main {2 3 public static void main(String[] args) {4 Singleton singleton = Singleton.getInstance();5 System.out.println(singleton.getDescription());
6 }7 }
结果展示:
单件模式:确保一个类只有一个实例,并提供一个全局访问点。
好啦,我们从上面的示例简单学习了单件模式;现在让我们来看一个更加复杂的示例
(巧克力工厂):
1 public class ChocolateBoiler { 2 private boolean empty; 3 private boolean boiled; 4 5 private ChocolateBoiler() { 6 empty = true; //刚开始锅炉是空的 7 boiled = false; 8 } 9 10 public void fill() {11 if (isEmpty()) { //在锅炉内填入原料时,锅炉必须是空的。一旦填入原料,就把empty和boiled标志设置好12 empty = false; 13 boiled = false;14 //在锅炉内填满巧克力和牛奶的混合物15 }16 }17 18 public void drain() {//锅炉排出时,必须是满的(不可以是空的)而且是煮沸过的。排出完毕后,吧empty设置为true19 if (!isEmpty() && isBoiled()) {20 // 排出煮沸的巧克力和牛奶21 empty = true;22 }23 }24 25 public void boil() { //煮混合物时,锅炉必须是满的,并且是没有煮过的。一旦煮沸后,就把boiled设为true26 if (!isEmpty() && !isBoiled()) {27 // 将炉内物煮沸28 boiled = true;29 }30 }31 32 public boolean isEmpty() {33 return empty;34 }35 36 public boolean isBoiled() {37 return boiled;38 }39 }
我们在有意识地防止不好的事情发生,但是如果同时存在两个ChocolateBoiler实例,
可能就会发生很糟糕的事情哦!
所以我们把这个类设计成单件:
1 public class ChocolateBoiler { 2 private boolean empty; 3 private boolean boiled; 4 private static ChocolateBoiler uniqueInstance; 5 6 private ChocolateBoiler() { 7 empty = true; 8 boiled = false; 9 }10 11 public static ChocolateBoiler getInstance() {12 if (uniqueInstance == null) {13 System.out.println("Creating unique instance of Chocolate Boiler");14 uniqueInstance = new ChocolateBoiler();15 }16 System.out.println("Returning instance of Chocolate Boiler");17 return uniqueInstance;18 }19 20 public void fill() {21 if (isEmpty()) {22 empty = false;23 boiled = false;24 // fill the boiler with a milk/chocolate mixture25 }26 }27 28 public void drain() {29 if (!isEmpty() && isBoiled()) {30 // drain the boiled milk and chocolate31 empty = true;32 }33 }34 35 public void boil() {36 if (!isEmpty() && !isBoiled()) {37 // bring the contents to a boil38 boiled = true;39 }40 }41 42 public boolean isEmpty() {43 return empty;44 }45 46 public boolean isBoiled() {47 return boiled;48 }49 }
测试类(Main.java):
1 public class ChocolateController { 2 public static void main(String args[]) { 3 ChocolateBoiler boiler = ChocolateBoiler.getInstance(); 4 boiler.fill(); 5 boiler.boil(); 6 boiler.drain(); 7 8 // 将返回已存在的实例,也就是boiler 9 ChocolateBoiler boiler2 = ChocolateBoiler.getInstance();10 }11 }
我们现在模仿了第一个项目,把它做成了单件,但是现在的这个类完美吗?不!当然
不完美,这不问题出现了:这个机器竟然允许在加热的时候继续加原料。
我们现在化身为JVM老看看问题出在哪里吧:
现在让我们开始解决问题,处理多线程(延迟同步,读完下面按段话就懂喽):
方法①:
1 public class Singleton { 2 private static Singleton uniqueInstance; 3 4 private Singleton() {} 5 6 public static synchronized Singleton getInstance() { 7 if (uniqueInstance == null) { 8 uniqueInstance = new Singleton(); 9 }10 return uniqueInstance;11 }12 13 //其他代码14 }
第六行:通过增加synchronized关键字到getInstance()方法中,我们迫使每个线程在
进入这个方法之前,要先等候别的线程离开该方法。也就是说,不会有两个线程可同时
进入这个方法。
但是,我们是否能改善多线程呢?
方法②(使用“急切”创建实例,而不是延迟实例的方法):
1 public class Singleton { 2 private static Singleton uniqueInstance = new Singleton(); 3 4 private Singleton() {} 5 6 public static synchronized Singleton getInstance() { 7 return uniqueInstance; 8 } 9 10 //其他代码11 }
方法③(双重检查加锁,在getInstance()中减少使用同步):
1 public class Singleton { 2 private volatile static Singleton uniqueInstance; 3 4 private Singleton() {} 5 6 public static synchronized Singleton getInstance() { 7 if (uniqueInstance == null) { 8 synchronized(Singleton.class){ 9 if(uniqueInstance == null){10 uniqueInstance = new Singleton();11 }12 }13 }14 return uniqueInstance;15 }16 17 //其他代码18 }
注:volatile关键词确保:当uniqueInstance变量被初始化成Singleton实例时,多个
县城正确的处理uniqueInstance变量。
我们现在来对比一下三个方法:
方法①同步getInstance方法:
这是保证可行的最直接的做法,对于巧克力锅炉似乎没有性能的考虑,所
以可以用这个方法
方法②急切实例化:
我们一定需要用到一个巧克力锅炉,所以静态的初始化实力并不是不行的。
虽然对于采用标准模式的开发人员来说,此做法可能稍微陌生一点儿。但也
是可行的。
方法③双重检查加锁:
由于没有性能上的考虑,所以这个方法似乎杀鸡用了牛刀。另外,采用这个方法还得确定使用的是Java5以上的版本。
原文链接:http://www.cnblogs.com/Trojan00/p/11331558.html
本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728