GOF中最简单的一个模式,只涉及到一个类。比如线程池、缓存等这些对象在应用只能被实例化一次,如果实例化多次,有可能会造成不可预测的后果。何谓单例?答曰:该类的对象只能被实例化一次。
程序猿:如果只是为了让对象被创建一次,那我们直接把对象放在全局变量在不就可以解决这类问题了?
大师:放在全局中的确可以解决该类问题,但还是存在一些缺陷。
程序猿:什么缺陷?
大师:你把对象放在全局变量中,意味着在程序启动的时候就必须要实例化,如果该对象创建的时间比较长而且
在创建出来的时候不一定就用得上,那就会消耗资源,全局静态变量的确是能解决问题,但单例模式也一个不错
的选择,你可以在需要的时候再创建它。
单例模式实现
类如何被实现化 : new MyInstance(); 如果另一个对象想创建怎么办?当然还是可以通过new 来再一次进行创建。只要是公共类,就可以被重复创建。那如果不是公共类又是一种什么情况呢?只有同一个包中的类可以实例化它,但是也可以被多次实例化。
如果有多年经验的你可能会想到 private MyInstance(),对了,如果应用私有构造函数,那就不能通过
new来随心所欲的进行实例化了。那你可能又有一个问题要问了,不能通过new来实例化对象,那通过什么方式来实例化对象?来看一下下面的代码你就会晃然大悟了。
1 public class MyInstance{ 2 3 private MyInstance(){ 4 } 5 // 6 public static MyInstance getMyInstance(){ 7 return new MyInstance(); 8 } 9 }
通过getMyInstance方法,就可以进行实例化了,但这个跟直接跟通过new创建有何区别呢,代码还没有完成,我们来进一步完善
public class MyInstance{ private static MyInstance instance; //通过私有构造函数,防止用户通过默认的构造函数来实例化对象 private MyInstance(){} public static MyInstance getMyInstance(){ if(instance==null){
System.out.print("myinstance is null"); instance =new MyInstance();
} return instance; } }
下面代码测试一下是否工作正常,建一个类来实例化MyInstance,然后在main中调用,
1 public class Test { 2 public void business(){ 3 MyInstance _instand1 = MyInstand.getMyInstance(); 4 MyInstance _instand2 = MyInstand.getMyInstance(); 5 } 6 }
1 public class Main { 2 3 public static void main(String[] args) { 4 //TODO Auto-generated method stub 5 Test t = new Test(); 6 t.business(); 7 } 8 9 }
代码执行后,你会看到控制台只输出一条“Myinstance is null“,说明MyInstance只被实例化了一次。到此单例模式基本完成,为什么是基本完成而不是已完成了呢。要知详情请看后文。
得意得太早
现实永远比想象残酷,当你以为上面的代码已完成交给你的其他猿类使用时,末日来临了。请猿们看下面的代码,看看将会发生什么。
1 public class Test extends Thread{ 2 3 @Override 4 public void run() { 5 // TODO Auto-generated method stub 6 super.run(); 7 MyInstance _instance = MyInstanece.getMyInstance(); 8 } 9 } 10 11 12 public class Main { 13 14 /** 15 * @param args 16 */ 17 public static void main(String[] args) { 18 // TODO Auto-generated method stub 19 Test t1 = new Test(); 20 t1.start(); 21 Test t2 = new Test(); 22 t2.start(); 23 } 24 25 }
执行以上代码发现了什么?出现了两条“myinstand is null”,你测试的没事,是出现两次,这就是线程做得手脚。
解决
既然是线程引起的,那我们就解决线程问题,在MyInstance的getMyInstance方法前加上“synchronized”,让线程同步,这种灾难将得到解决
1 public class MyInstance { 2 private static MyInstance _instance; 3 private MyInstance(){} 4 public static synchronized MyInstand getMyInstance(){ 5 if(_instance==null){ 6 System.out.print("myinstand is null"); 7 _instance =new MyInstance(); 8 } 9 return _instance; 10 } 11 }
现在再执行代码,发现只实例化了一次。
一波未平一波又起
通过线程同步解决了上面出现的问题,但新的问题又出现在了我们的眼前,那就是程序的性能降低了。--未完待续