单例模式
懒汉式和饿汉式的区别
懒汉式:需要使用时才加载
饿汉式:不管要不要使用时,在类装载的时候就创建一个静态堆对象
1 . 懒汉式(线程不安全)
但是在多线程时候,很多线程不能正常工作
public class singlePattern1 {
private static singlePattern1 instance; //采用一个静态对象
public static singlePattern1 getInstance(){
if(instance == null){ // 如果没有该对象的实例
instance = new singlePattern1(); // 实例化该对象
}
return instance;
}
}
2 . 懒汉式(线程安全)
这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading
但是,遗憾的是,效率很低,99%情况下不需要同步。
public class singlePattern2 {
private static singlePattern1 instance; //采用一个静态对象
public static synchronized singlePattern1 getInstance() {//持有该对象的锁的线程可以执行该对象方法,,使线程操作有序的操作该方法
if (instance == null) { // 如果没有该对象的实例
instance = new singlePattern1(); // 实例化该对象
}
return instance;
}
}
3 . 饿汉式(基于classloder机制)
这种方式基于classloder机制避免了多线程的同步问题
不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种
在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其 他的方式(或者其他的 静态方法)导致类装载这时候初始化instance显然没有达到lazy loading的效果
public class singlePattern3 {
private static singlePattern3 instance = new singlePattern3();//在类的装载就直接实例化
public static singlePattern3 getInstance() {
return instance;
}
}
4 . 饿汉式(类加载就执行)
和第3种差不多,java的静态代码块,也是在加载的时候就执行,即在装载类的时候就实例化对象,初始化对象就实例化对象
public class singlePattern4 {
private static singlePattern4 instance = null;
static { // 静态代码块 ,,加载类的时候,就执行
instance = new singlePattern4();
}
public static singlePattern4 getInstance() {
return instance;
}
}
5 .懒汉式(基于classloder的机制)
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,
它跟第三种和第四种方式不同的是(很细微的差别):
第三种和第四种方式是只要singlePattern5类被装载了,那么instance就会被实例化(没有达到lazy loading效果)
而这种方式是singlePattern5类被装载了,instance不一定被初始化
因为singletonHolder类没有被主动使用,只有显示通过调用getInstance方法时, 才会显示装载singletonHolder类,从而实例化instance。
想象一下,如果实例化instance很消耗资源,我想让他延迟加载
另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载
那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。
public class singlePattern5 {
private static class singletonHolder {//静态方法私有化
private static final singlePattern5 instance = new singlePattern5();
}
public static final singlePattern5 getInstance() {
return singletonHolder.instance;
}
}
6 . 枚举(安全性高,防止反射打破单例)
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,使用枚举实现的单例模式,不但可以防止利用反射强行构造单例子对象,而且枚举类对象在被反序列化的时候,保证反序列的返回结果是同一对象,
对于其他方法实现单例模式,如果既想要做到可序列化,又想反序列化为同一对象则必须实现readResolve方法
public enum singlePattern6 {
INSTANCE;
}
7 . 双重校验机制(非常安全)
使用双重锁检测机制,确保并发情况下instance 对象不会被重复的初始化,
使用volatile 修饰符,防止指令重排引发初始化问题
保证线程的安全
public class singlePattern7 {
private singlePattern7() {
}
private volatile static singlePattern7 instance = null;
public static singlePattern7 getInstance() {
if (instance == null) { // 双重检验机制
synchronized (singlePattern7.class) { //设置同步锁
if (instance == null) { // 双重检验机制
instance = new singlePattern7();
}
}
}
return instance;
}
}
总结:
对于以上七种单例,第一种是最不推荐的,在多线程并发的时候,单例直接就不能维持,其它六种
在一些条件下能够保证单例,为什么说在一些条件下能保证单例? 是因为单例即使很安全,但是反
射能打破单例。枚举的情况就不会被打破单例。
反射打破单例
Constructor con = singlePattern.class.getDeclaredConstructor();//获取构造器
con.setAccessible(true); //设置为该对象为可访问
//构造两个对象
singlePattern instance1= (singlePattern)con.newInstance();
singlePattern instance2 = (singlePattern)con.newInstance();
可以通过类的反射获取类的实例对象,即单例被打破了,但是枚举类不可以反射。所以枚举,不能
被反射打破。