一说到单例模式,我想大家首先想到的是懒汉式、恶汉式吧!至于登记式(淘汰的模式,可忽略)。java
单例模式有如下特色:
一、单例类只能有一个实例。
二、单例类必须本身建立本身的惟一实例。
三、单例类必须给全部其余对象提供这一实例。安全
1、懒汉式单例并发
先写一个懒汉式的单例模式。ide
public class Singleton { private Singleton() {} private static Singleton single=null; public static Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; } }
Singleton经过将构造方法限定为private避免了其余类经过访问构造器进行实例化,在同一个虚拟机范围内,Singleton的惟一实例只能经过静态的getInstance()方法进行访问。性能
可是上面的代码是不考虑线程安全的状况下,也就是说,该实例是存在线程安全的。在并发的状况下是可能出现这种状况,就是a线程先进入getInstance()方法在建立实例化的时候,也就是还没建立成功,b线程也进入了getInstance()方法,这个时候a线程实例还没建成功,b线程判断single为空也开始建立实例,致使会出现建立出两个实例来。ui
解决方式有三种:spa
public static synchronized Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; }
加上synchronized关键字,并发的时候也只能一个一个排队进行getInstance()方法访问。线程
public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
双重检查锁定,这种方式会优于上面一种方式,在并发量高的状况下,不须要排队进getInstance()方法合理利用系统资源,性能上会优于上面一种。code
public class Singleton { private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return LazyHolder.INSTANCE; } }
静态内部类实现单例模式,这种方式优于上面两种方式,他即实现了线程安全,又省去了null的判断,性能优于上面两种。对象
2、饿汉式单例
public class Singleton { private Singleton() {} private static final Singleton single = new Singleton(); public static Singleton getInstance() { return single; } }
饿汉式是静态加载的时候实,不须要担忧线程安全问题。
3、枚举单例模式
以上两种方式是在不考虑放射机制和序列化机制的状况下实现的单例模式,可是若是考虑了放射,则上面的单例就没法作到单例类只能有一个实例这种说法了。事实上,经过Java反射机制是可以实例化构造方法为private的类的。这也就是咱们如今须要引入的枚举单例模式。
public enum EnumSingleton { INSTANCE; public EnumSingleton getInstance(){ return INSTANCE; } }
举个例子就能知道上面的单例不是很安全,以双重检索的单例模式为例子,我利用放射,可以建立出新的实例:
public static void main(String[] args) throws Exception { Singleton s=Singleton.getInstance(); Singleton sual=Singleton.getInstance(); Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton s2=constructor.newInstance(); System.out.println(s+"\n"+sual+"\n"+s2); System.out.println("正常状况下,实例化两个实例是否相同:"+(s==sual)); System.out.println("经过反射攻击单例模式状况下,实例化两个实例是否相同:"+(s==s2)); }
结果为:
cn.singleton.Singleton@1641e19d cn.singleton.Singleton@1641e19d cn.singleton.Singleton@677323b6 正常状况下,实例化两个实例是否相同:true 经过反射攻击单例模式状况下,实例化两个实例是否相同:false
因而可知双重检索模式不是最安全的,没法避免反射的攻击。
咱们检测一下枚举的单例模式
public static void main(String[] args) throws Exception{ EnumSingleton singleton1=EnumSingleton.INSTANCE; EnumSingleton singleton2=EnumSingleton.INSTANCE; System.out.println("正常状况下,实例化两个实例是否相同:"+(singleton1==singleton2)); Constructor<EnumSingleton> constructor= null; constructor = EnumSingleton.class.getDeclaredConstructor(); constructor.setAccessible(true); EnumSingleton singleton3= null; singleton3 = constructor.newInstance(); System.out.println(singleton1+"\n"+singleton2+"\n"+singleton3); System.out.println("经过反射攻击单例模式状况下,实例化两个实例是否相同:"+(singleton1==singleton3)); }
结果会报Exception in thread "main" java.lang.NoSuchMethodException。出现这个异常的缘由是由于EnumSingleton.class.getDeclaredConstructors()获取全部构造器,会发现并无咱们所设置的无参构造器,只有一个参数为(String.class,int.class)构造器,并且在反射在经过newInstance建立对象时,会检查该类是否ENUM修饰,若是是则抛出异常,反射失败。因此枚举是不怕发射攻击的。
newInstance方法源码:
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }