单例模式通常分懒汉和饿汉两种模式。
1、懒汉单例模式
可以理解为懒汉模式创建的类,非常懒,只有在被调用时才会判断自身是否已有对象,如果没有就立即创建自身对象,然后返回。如果已有对象就立即返回。
public Class Single(){
private static Single intance = null;
private Single();//私有默认构造器
public static synchronized Single getIntence(){//添加同步锁,防止多线程同时进入造成多次实例化
if(intence==null){
intance = new Single();
}
return intance;
}
}
优点:调用时才会初始化,避免资源浪费。
缺点:加了synchronized锁,影响效率 。
2、饿汉单例模式
可以理解为饿汉模式创建的类都很饥渴,在类加载的时候就创建对象。
public Class Single(){
public static Single intance = new Single();//当类加载时就调用自身
private Single(){};
public static Single getIntance(){
return intance;
}
}
优点:1、线程安全,在类加载时创建实例不存在在运行过程中,多个线程创建多次实例,避免了多线程同步的情况。
2、调用时速度快。
缺点:资源效率不高,如果之后没有用到getinstance()的话,会造成资源浪费。
下面介绍几种单例模式为常用细分类:
1、懒汉模式不安全(lazy loading)
严格意义上不是单例模式,这种模式不能在多线程环境下使用。
public Class Single(){
private static Single instance;
private Single();
public static Single getInstance(){ //未加安全锁,大家一起用
if(instance == null){
instance = new Single();
}
return instance;
}
}
很明显这种方式不适合在多线程下使用。
下面这几种都适合多线程使用。
2、双重检验锁/双检锁
他是懒汉的一种,这种方式采用了双锁机制,在安全下保持高性能。
public Class Single(){
private static Single instance;
private Single();
private static Single getInstance(){
if(instance == null){
synchronized(Single.class);
if(instance == null){
instance = new Single();
}
}
}
}
可在实例域需要延迟初始化的时候使用。
3、登记式/静态内部类
对静态域进行延迟初始化应用这种方式而不用双检锁。
public Class Single(){
private static class SingleHandler(){
private static final Single() instance = new Single();
}
private Single(){}
public static final Single getInstance(){//吐槽下 csdn这个编辑器真滴不咋好使
return SingleHandler.instance;
}
}
这种方式就是Single类被加载后,instance不一定被实例化。如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。
4、枚举
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。他不具有懒汉的特性。由于 JDK1.5 之后才加入 enum 特性,在实际工作中,很少用。
不能通过 reflection attack 来调用私有构造方法。
public enum Single(){
instance;
public void method(){
}
}
总结
通常,不建议使用懒汉模式不安全和synchronizad懒汉方式,建议使用饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用登记式。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用双检锁方式。