无论什么开发中,设计模式都起着关键的作用,其中比较常用的当属单例了,所谓单例,就是让一个类在项目中只存在一个对象,即使用到这个类的地方很多,也只存在一个对象。但是为什么要这样呢,为什么只创建一个对象呢,多个不也行吗?这个就要结合实际来说了,有些对象我们确实只需要一个,比如说线程池、缓存、硬件设备,如果多个实例就可能会导致冲突,出现运行结果不一致的现象。
一、单例的基本框架
当然,单例有很多种实现形式,最基本的框架大致如下:
1 /** 2 * 普通单例: 3 * 只使用在单一线程,多线程时可能会出现多个对象 4 * @author 刘伟 2015/10/13 5 */ 6 public class SimpleSingleTon { 7 private static SimpleSingleTon instance = null; 8 9 public static SimpleSingleTon getInstance() { 10 if (instance == null) { 11 instance = new SimpleSingleTon(); 12 } 13 return instance; 14 } 15 16 private SimpleSingleTon() { 17 } 18 }
上面代码即为单例的一个基本的实现形式,很轻松就能看出在程序中首次使用这个类的代码通过getInstance()方法就能实例化这个类从而获得类的对象,但以后再调用getInstance()时就不会实例化,而是直接获得已经存在的对象。
二、单例模式的优化
这段代码在单一线程中执行是没有问题的,但如果是在多线程中就可能会出现两个或多个对象,试想一下如果恰好有两个线程同时进入了getIntance()得if语句里面,这时候就会实例化两次SimpleSingleTon,因为首次执行getInstance()时instance是null,所以这种情况是可能发生的。那么怎么避免这种情况发生呢,可以使用以下几种方法:
这种方法是直接在单例类里面吧静态变量直接实例化,这样无论是多线程还是单线程都能保证只有一个对象了,缺点就是会对内存造成一定的浪费。
1 /** 2 * 单例优化--急切创建对象 3 * 4 * @author codingblock 2015/10/13 5 */ 6 public class SingleTon { 7 private static SingleTon instance = new SingleTon(); 8 9 public static SingleTon getInstance() { 10 System.out.println("instance:" + instance); 11 return instance; 12 } 13 14 private SingleTon() { 15 16 } 17 }
为了在多线程中不让两个线程同时执行getInstance()方法,可以为此方法添加一同步锁,这样就能避免此情况发生了。代码如下:
1 /** 2 * 单例优化--添加同步锁 3 * 可以在多线程中运行 4 * @author 刘伟 2015/10/13 5 */ 6 public class SimpleSyncSingleTon { 7 8 private static SimpleSyncSingleTon instance = null; 9 10 public static synchronized SimpleSyncSingleTon getInstance() { 11 if (instance == null) { 12 instance = new SimpleSyncSingleTon(); 13 } 14 return instance; 15 } 16 17 private SimpleSyncSingleTon() { 18 } 19 }
这样就可以保证在多线程中也只会创建一个对象,但同步锁是比较耗费资源的,如果在程序中频繁地获取对象,这样的话效率就大大地降低了。所以说,在单例中添加同步锁的方法比较适用于对对象获取不是很频繁地情况。
首先需要在对象变量前面添加一个volatile关键字,这个是为了通知编译器线程安全用的。然后再getInstance检查两次,具体代码如下:
1 /** 2 * 单例优化--双重检查加锁法 3 * 可以在多线程中运行 4 * @author 刘伟 2015/10/13 5 */ 6 public class CheckAgainSingleTon { 7 8 private volatile static CheckAgainSingleTon instance = null; 9 10 public static synchronized CheckAgainSingleTon getInstance() { 11 if (instance == null) { 12 synchronized (CheckAgainSingleTon.class) { 13 if (instance == null) { 14 instance = new CheckAgainSingleTon(); 15 } 16 } 17 } 18 return instance; 19 } 20 21 private CheckAgainSingleTon() { 22 } 23 }
通过这个方法程序在获取对象时无论怎么样都只会进入加锁区一次,例如最开始两个线程在竞争时,其中一个线程进入了加锁后创建了对象,以后所有的进程在执行getInstance方法时直接判断instance非null,就能直接返回对象了,不需要再进入加锁区了。这样即使程序频繁地获取对象也不会再进入加锁区了,相对第二种方法就大大节省了资源。