赞
踩
所谓单例,指的就是单实例,有且仅有一个类实例,该类提供了一个全局访问点供外部获取该实例,这个单例不应该由人来控制,而应该由代码来限制,强制单例。
单例有其独有的使用场景,一般是对于那些业务逻辑上限定不能多例只能单例的情况,例如:类似于计数器之类的存在,一般都需要使用一个实例来进行记录,若多例计数则会不准确。
/** * 饿汉式单例 */ public class Hungry { /** * 很明显,一进来就加载对象,存在浪费空间 * 这时候我们需要一个用的时候才加载对象,不用不加载 * 这个时候可以使用懒汉式单例 */ private Hungry(){ } public final static Hungry hungry = new Hungry(); public static Hungry getInstance(){ return hungry; } }
/** * 懒汉式单例 */ public class LazyMan { /** * 这种方式,单线程是OK的,多线程就不行了 */ private LazyMan(){ System.err.println(Thread.currentThread().getName()+"-----"); } public static LazyMan lazyman; public static LazyMan getInstance(){ if(lazyman==null){ lazyman = new LazyMan(); } return lazyman; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ LazyMan.getInstance(); }).start(); } } }
可以看到,在多线程的情况下就不行了
使用双重检查锁模式
public static LazyMan getInstance(){ if(lazyman==null){ synchronized (LazyMan.class){ /** * 但是也有一个问题,就是不是一个原子性操作 * 初始化对象正常顺序 * 1、分配内存空间 * 2、执行构造方法,初始化对象 * 3、把这个对象指向这个空间 * * 那么这个过程中可能会产生指令重排的现象 * 132 B * 此时LazyMan还没有完成构造 可以是用volatile关键字来解决 * volatile:可以防止指令重排 * */ if(lazyman==null){ lazyman = new LazyMan(); } } } return lazyman; }
使用volatile关键字
/** * 懒汉式单例 */ public class LazyMan { /** * 这种方式,单线程是OK的,多线程就不行了 */ private LazyMan(){ System.err.println(Thread.currentThread().getName()+"-----"); } public volatile static LazyMan lazyman; public static LazyMan getInstance(){ if(lazyman==null){ synchronized (LazyMan.class){ /** * 但是也有一个问题,就是不是一个原子性操作 * 初始化对象正常顺序 * 1、分配内存空间 * 2、执行构造方法,初始化对象 * 3、把这个对象指向这个空间 * * 那么这个过程中可能会产生指令重排的现象 * 132 B * 此时LazyMan还没有完成构造 可以是用volatile关键字来解决 * volatile:可以防止指令重排 * */ if(lazyman==null){ lazyman = new LazyMan(); } } } return lazyman; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ LazyMan.getInstance(); }).start(); } } }
OK,没问题。
但是我们Java学过一个特别暴力的获取对象的方式那就是反射!!!!
使用反射以上方法也不安全了!!!
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.err.println(instance1);
System.err.println(instance2);
}
可以看到两个对象不一致!完了,又被破坏了
在构造方法中判断一下对象是否已经存在
private LazyMan(){
synchronized (LazyMan.class){
if(lazyman!=null){
throw new RuntimeException("不要试图使用反射进行破环!");
}
}
}
但是,之前有一个对象是我们手动创建的,那如果两个对象都使用反射进行创建呢?
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
System.err.println(instance1);
System.err.println(instance2);
}
完了,又被破解~~~
可以定义一个布尔类型的变量进行判断
private static boolean isinstance =false;
private LazyMan(){
synchronized (LazyMan.class){
if(isinstance==false){
isinstance=true;
}else{
throw new RuntimeException("不要试图使用反射进行破坏!");
}
}
}
ok,成功,以上方法已经可以解决一大部分安全问题了
注意:如果你定义的变量被暴露了,那么还是可以通过反射进行破坏
那么这个万恶的反射到底如何才可以不被破坏呢?查看反射中的newInstance()源码
通过源码可知,如果是个枚举类那么反射就破解不了
枚举类
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { EnumSingle instance1 = EnumSingle.INSTANCE; Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class); declaredConstructor.setAccessible(true); EnumSingle enumSingle = declaredConstructor.newInstance(); System.err.println(instance1); System.err.println(enumSingle); } }
果然跟源码中的一样,枚举类你不能通过反射破坏
/** * 静态内部类 */ public class Hodel { private Hodel(){ } public static Hodel getInstance(){ return InnerClass.hodel; } public static class InnerClass{ private final static Hodel hodel = new Hodel(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。