单例模式的优缺点,最好能用枚举作为单例模式
1、懒汉式单例:
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为空也开始创建实例,导致会出现创建出两个实例来。
2、上面的例子有三种解决方案如下:
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
加上synchronized关键字,并发的时候也只能一个一个排队进行getInstance()方法访问。
3、public static Singleton getInstance() {
if (single == null) {
synchronized (Singleton.class) {
if (single == null) {
single = new Singleton();
}
}
}
return single;
}
双重检查锁定,这种方式会优于上面一种方式,在并发量高的情况下,不需要排队进getInstance()方法合理利用系统资源,性能上会优于上面一种。
4、class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
静态内部类实现单例模式,这种方式优于上面两种方式,他即实现了线程安全,又省去了null的判断,性能优于上面两种。
5、饿汉式单例:
class Singleton {
private Singleton() {}
private static final Singleton single = new Singleton();
public static Singleton getInstance() {
return single;
}
}
饿汉式是静态加载的时候实例化,不需要担心线程安全问题。
6、以上方式是在不考虑放射机制和序列化机制的情况下实现的单例模式,但是如果考虑了放射,则上面的单例就无法做到单例类只能有一个实例这种说法了。我们以饿汉式单例为例子,我利用放射,能够创建出新的实例来代码如下:
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));
}
7、结果如下,尤此可得出以上的单例无法避免反射的恶意攻击。
8、我们再试试枚举模式:
enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
并用相同的方式攻击。
9、可结果却是报Exception in thread "main" java.lang.NoSuchMethodException: com.ffcs.maintest.EnumSingleton.<init>()异常。出现这个异常的原因是因为EnumSingleton.class.getDeclaredConstructors()获取所有构造器,会发现并没有我们所设置的无参构造器,只有一个参数为(String.class,int.class)构造器,最重要的是反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。所以就算你反射的时候设置有参构造器也会反射失败。所以枚举是不怕反射攻击的。