DengQN·一个普通程序员;
设计模式-单例模式
2018-09-30 17:00 71
#对象#单例#枚举#方法#类#实现#序列化#加锁

有时候,我们希望某一个类只能产生一个对象。比如说一个学校的校长只能有一个。

初步

要一个类只能有一个实例,也就是说我们要把它的构造器隐藏起来:private Something(){}

然后提供一个对外的方法,用来实际的产生/返回对象。也就是说我们可以得到下边的实现:

public class Something {
    private static Something INSTANCE = null;
    private Something(){}

    public static Something getINSTANCE() {
        if (INSTANCE == null){
            INSTANCE = new Something();
        }
        return INSTANCE;
    }

}

这是一个单例模式的简单实现。我们发现,每次getINSTANCE()的时候,都要判断INSTANCE == null

所以我们也可以这样(如果你的单例对象足够小,而且运行期间都需要):

public class Something {
    private static Something INSTANCE = new Something();
    private Something(){}

    public static Something getINSTANCE() {
        return INSTANCE;
    }
}

多线程下的单例模式

上边的单例模式实现在多线程下是不安全的。因为不能保证INSTANCE的值不会改变。解决线程问题的方法,很简单,加锁。

比如,我们给getINSTANCE方法加锁。:

public static synchronized Something getINSTANCE() {
    if (INSTANCE == null){
       INSTANCE = new Something();
    }
    return INSTANCE;
}

或者在判断null的时候加锁:

public static Something getINSTANCE() {
    synchronized (Something.class){
        if (INSTANCE == null){
            INSTANCE = new Something();
        }
    }
    return INSTANCE;
}

但是,这样有个问题,就是每次获取实例的时候,都有锁操作,会耗费资源,假如,这个方法被频繁使用,会有性能问题。

把代码改一下:

public static Something getINSTANCE() {
    if (INSTANCE == null){
        synchronized (Something.class){
            if (INSTANCE == null){
                INSTANCE = new Something();
            }
        }
    }
    return INSTANCE;
}

这样写的好处是:只有在INSTANCE为null的时候才会加锁处理。也就是所只起作用一次。

使用枚举实现单例

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

使用枚举来实现单例,在类真正被用到的时候才会加载对象(利用类加载机制),加载过程是同步的,是线程安全的。

前边的单例在面对序列化、反序列化的时候还是不安全的,但是对于枚举,直接规定不能反序列化枚举对象。。。

public abstract class Enum<E extends Enum<E>>  implements Comparable<E>, Serializable {  
   // 略
  
    // 不允许反序列化枚举对象
    private void readObject(ObjectInputStream in) throws IOException,  
        ClassNotFoundException {  
            throw new InvalidObjectException("can't deserialize enum");  
    }  
  
    // 不允许反序列化枚举对象  
    private void readObjectNoData() throws ObjectStreamException {  
        throw new InvalidObjectException("can't deserialize enum");  
    }  
 
    // 枚举类不可以有finalize方法,子类不可以重写该方法  保证实例的对象唯一
    protected final void finalize() { }  
}