单例模式为23种经典设计模式中最简单的一种,然而很多人实现的单例并不是真正的单例,或者说是"不安全"的单例,下面我们就从最漏的单例说起,最后为现在业界使用的单例实现方法:
直接上代码:
第一种:
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if (instance==null){
return instance = new Singleton();
}
return instance;
}
}
下面说下这种方法的缺点,其实这种方法并不是真正的单例,为什么呢?因为它的对象的创建不是完整的原子,所以在多线程竞争下,可能导致多个实例
第二种:
继第一种,因为创建的时候不能保证原子性,所以我们可以通过加同步锁
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if (instance==null){
return instance = new Singleton();
}
return instance;
}
}
但是这种方法明显也有缺点,并且是致命的,因为方法加同步,每次都要同步判断,太浪费时间,太慢,而这个慢可能造成一些列意想不到的结果
第三种:
继第二种,因为方法加同步太慢,所以呢,有人就说,那么我可不可以不在方法上加锁,在代码块中加锁呢?
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
synchronized (Singleton.class){
if (instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
如上,加双重校验锁,注意方法中进行两次校验,这样如果instance不为null就直接返回,不用走同步,如果instance!=null再进行同步校验,这样的确提高了效率,只有第一次加载的时候慢点,其他时候就根本不用走同步,的确快!但是这种方法就没有问题了吗?大家想一想!假如在高并发的情况下,假如我A,B线程同时进入方法,假如A拿到锁之后呢,B等待对吧!但是,假如我A刚执行完同步代码块,但是这时候,我还没有把instance改变之后的值加载到公共内存中,这时候B进入同步代码块了,因为A还没有将代码块写入公共内存,所以B进来之后,instance依然为null,又会进行创建一次,这时候发生了什么?所以我们要保证A对内存的修改对B是可见的.
第四种:
继第三种,在声明instance的时候加上volatile关键字,其他不变,但是这样写太麻烦了有没有简单的办法,不使用同步锁的呢?有
第五种:静态内部类
public class Singleton{ private Singleton(){} public static Singleton getInstance(){ return Instance.instance; } private static class Instance{ private static Singleton instance = new Singleton(); } }
解释下,为什么静态内部类可以,因为静态内部类在jdk类加载的时候,只会被加载一次,所以,自然也就是单例了.
貌似现在一切问题都解决了,貌似"完美"了,但真的是这样么?no!!!不要忘了java中有反射,你不是构造私有化么,我用反射把你私有改为共有,我不就可以创建实例了么?所以,以上所有方法貌似都不可以了!那该怎么办呢?如此就引申出最后一种解决方法了.
第六种:枚举
public enum Singleton{ INSTANCE; public void doSomething(){ } }这种在调用的时候,完全可以Singleton.INSTANCE.doSomething()来调用
下面解释下为什么可以啊,因为枚举,在java中天生就是构造私有的,枚举不允许有共有构造,所以即使反射也不可以,所以就可以用来做单例的载体,并且枚举可以声明一些其他的方法,并进行处理所以就完全可以实现单例!
下面附上测试代码,和测试截图
为了测试我们把单例中添加如此两句输出语句:
public enum Singleton{ INSTANCE; private Singleton(){ System.out.println("这是构造方法..."); } public void doSomething(){ System.out.println("这是业务..."); } }
测试代码:
public static void main(String[] args){ for(int i=0;i<20;i++){ new Thread(new Runnable() { @Override public void run() { try { Thread.currentThread().sleep(1000*3); } catch (InterruptedException e) { e.printStackTrace(); } Singleton.INSTANCE.doSomething(); } }).start(); } }测试结果:
看测试结果就可以看出,构造只被执行一次,并且枚举是java内部定义的,你去反射吧!
如果以上见解有不到的地方请谅解,如果有说错的地方请指出,谢谢!