设计模式中最简单的设计模式估计就是单例了。单例模式保证创建类的对象在整个程序生命周期中只有一个唯一的引用,目前公认的实现方式有两种。第一种即懒惰模式,也即是当需要创建的时候我们才会创建出这个唯一的对象,实现如下:
public class LazySigleton {
private static LazySigleton singleton;
private int number = 0;
private LazySigleton(){
number++;
}
public int getNumber() {
return number;
}
public static LazySigleton getInstance(){
if(singleton == null){
singleton = new LazySigleton();
}
return singleton;
}
}
实现方式很简单,将构造方法设置为private,这样只能在本class里访问到构造方法,也就是说只能在本class的方法里才能new这个类的对象。这样我们对外就可以提供一个静态的方法暴露给外界让,在这个静态方法里返回一个new的对象。并且只有在我们使用这个实例的时候才会初始化这个对象。貌似代码很完美,但是如果我们在多线程的情况下,是否会new非单一的对象来呢。我们来分析一下:当线程A运行到getInstance,首先判断一下当前singleton是否为null。因为是第一访问这个方法,所以singleton == null。可就当线程A要new LazySigleton() 。这时候半路杀出个线程B,线程B抢占了线程A的资源,也运行到了getInstance方法,因为这时候线程A还没有new LazySigleton()理所当然就new LazySigleton()。等线程A恢复过来,也会继续执行下面的代码,这时候就会new 出第二个new 出第二个LazySigleton。这样问题就出现了。怎么解决呢?
单例模式的第二种实现方式饥饿模式就可以解决这个问题,代码如下:
public class HungerSigleton {
private static HungerSigleton singleton = new HungerSigleton();
private int number = 0;
private HungerSigleton(){
number++;
}
public static HungerSigleton getInstance(){
return singleton;
}
}
由于在编译时期就把对象new出来了,所以就规避了多线程中new出多个对象的可能。是否还有其他的解决方案吗?java对多线程同步有非常好的支持,所以我们可以往这方面考虑一下:
package com.pattern;
public class SafeLazySigleton {
private static SafeLazySigleton singleton;
private int number = 0;
private SafeLazySigleton(){
number++;
}
public synchronized static SafeLazySigleton getInstance(){
if(singleton == null){
singleton = new SafeLazySigleton();
}
return singleton;
}
}
貌似能否解决这个问题,但是我们再仔细想一下。如果getInstance被频繁的调用,synchronized会非常消耗性能,而我们只需要在第一次new的时候才会考虑到同步。所以我们可以继续改造一下程序:
package com.pattern;
public class SafeLazySigleton {
private volatile static SafeLazySigleton singleton;//防止多线程读取缓存
private int number = 0;
private SafeLazySigleton(){
number++;
}
public static SafeLazySigleton getInstance(){
if(singleton == null){//只有当第一new的时候才会考虑同步
synchronized (SafeLazySigleton.class) {
if(singleton == null){//再次判断一下
singleton = new SafeLazySigleton();
}
}
}
return singleton;
}
}
这样,加了双重锁,即使在多线程下我们的单例也一样健壮。
现在我们总结一下:
- 简单的lazy模式是我们最常用的,只有在我们需要单例的时候才会创建单例。但是在多线程情况下有可能出现多个实例。
- 饥饿模式在编译时期就创建了我们需要的实例,可以解决多线程下多个实例的问题。
- 同步getInstance方法,也可以解决多线程同步问题,但是频繁调用会有性能上的问题,如果你的单例对系统开销消耗不大,也可以使用这个模式。
- 双重锁机制即能解决多线程问题也解决了性能问题,但是只能用在JDK1.5及以后的版本。