单例模式

1,饿汉试

线程安全

public class ThreadPool {

    private static ThreadPool threadPool = new ThreadPool();

    private ThreadPool() {

    }
    public static ThreadPool getInstance() {
        return threadPool;
    }

}

2,懒汉式

线程不安全,加双锁

public class ThreadPool {

    private static ThreadPool threadPool;

    private ThreadPool() {

    }
    public static synchronized ThreadPool getInstance() {
        if (threadPool == null) {
            synchronized (ThreadPool.class) {
                if (threadPool == null) {
                    threadPool = new ThreadPool();
                }
            }

        }
        return threadPool;
    }

}

3,枚举   枚举类型本身JVM 就会保证其是单例

public class SpringIOC {

    private SpringIOC() {

    }
    
    public static SpringIOC getInstance(){
        return SingleTon.INSTANCE.getInstance();
    }

    static enum SingleTon {
        INSTANCE;
        private SpringIOC springIOC;

        private SingleTon() {
            springIOC = new SpringIOC();
        }

        public SpringIOC getInstance() {
            return this.springIOC;
        }

    }

}

 4,静态内部类

public class TaskManager {

    private TaskManager() {

    }

    private static class TaskManagerHolder {
        private static final TaskManager taskManager = new TaskManager();
    }

    public static TaskManager getInstance() {
        return TaskManagerHolder.taskManager;
    }

}

单例的优化,防止被外部攻击

通过反射能够攻击有些单例模式,生成新的对象,以饿汉式为例:

public class ThreadPool {

    private static boolean flag = false;

    private static ThreadPool threadPool = new ThreadPool();
    
//无参构造方法
private ThreadPool() { } public static ThreadPool getInstance() { return threadPool; } public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { ThreadPool instance1 = ThreadPool.getInstance(); Class<?> clazz = Class.forName("com.hella.thread.pattern.ThreadPool"); ThreadPool instance2 = (ThreadPool) clazz.newInstance(); System.out.println(instance1 == instance2 ); //false 单例被攻击 } }

因为通过new 生成的关键字,通过调用public 的构造方法

通过反射 Class 对象下的newInstance() 方法是通过空的构造方法生成的对象,无论访问修饰符是public 或者 private

那若是带参数的构造方法,可以攻击吗? 也是可以的! 

     通过 getDeclaredConstructor 获取到构造方法,也是可以生成对象

public class ThreadPool {

    private String username;

    private static boolean flag = false;

    private static ThreadPool threadPool = new ThreadPool("Chris");

    private ThreadPool(String username) {
        this.username = username;
    }

    public static ThreadPool getInstance() {
        return threadPool;
    }

    public static void main(String[] args) throws Exception {

        ThreadPool instance1 = ThreadPool.getInstance();
        Class<?> clazz = Class.forName("com.hella.thread.pattern.ThreadPool");
        Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
        ThreadPool instance2 = (ThreadPool) constructor.newInstance("Chris");
        System.out.println(instance1 == instance2);  //false  单例已经被攻击
    }
}

通过指定一个flag ,代表只能调用一次构造方法,来防止被攻击

public class ThreadPool {

    private String username;

    private static boolean flag = false;

    private static ThreadPool threadPool = new ThreadPool("Chris");

    private ThreadPool(String username) {
        if (!flag) {
            this.username = username;
            flag = true; // 类加载的时候,jvm 会第一次调用生成静态对象,将flag 改为true,意思是只允许成功调用一次
        } else {
            throw new RuntimeException("单例正在被攻击");
        }
    }

    public static ThreadPool getInstance() {
        return threadPool;
    }

    public static void main(String[] args) throws Exception {

        ThreadPool instance1 = ThreadPool.getInstance();
        Class<?> clazz = Class.forName("com.hella.thread.pattern.ThreadPool");
        Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
        ThreadPool instance2 = (ThreadPool) constructor.newInstance("Chris");
        System.out.println(instance1 == instance2);
    }
}

那是不是代表这种防止被攻击就安全了呢?不是!

可以获取到flag 的值,再重新赋值,就可以通过反射继续生成对象了,从而破环单例。

public class ThreadPool {

    private String username;

    private static boolean flag = false;

    private static ThreadPool threadPool = new ThreadPool("Chris");

    private ThreadPool(String username) {
        if (!flag) {
            this.username = username;
            flag = true; // 类加载的时候,jvm 会第一次调用生成静态对象,将flag 改为true,意思是只允许成功调用一次
        } else {
            throw new RuntimeException("单例正在被攻击");
        }
    }

    public static ThreadPool getInstance() {
        return threadPool;
    }

    public static void main(String[] args) throws Exception {

        ThreadPool instance1 = ThreadPool.getInstance();
        Class<?> clazz = Class.forName("com.hella.thread.pattern.ThreadPool");
        Field[] fields = clazz.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
// 私有属性必须要通过setAccessible(true)来访问 fields[i].setAccessible(
true); if (fields[i].getName().equals("flag")) { fields[i].setBoolean(instance1, false); } } Constructor<?> constructor = clazz.getDeclaredConstructor(String.class); ThreadPool instance2 = (ThreadPool) constructor.newInstance("Chris"); System.out.println(instance1 == instance2); } }

静态内部类的防止攻击:

public class TaskManager {

    private TaskManager() {
        if (TaskManagerHolder.taskManager == null) {
            return;
        }
        throw new RuntimeException("单例正在被攻击");
    }

    private static class TaskManagerHolder {
        private static final TaskManager taskManager = new TaskManager();
    }

    public static TaskManager getInstance() {
        return TaskManagerHolder.taskManager;
    }

    public static void main(String[] args)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        TaskManager instance1 = TaskManager.getInstance();
        Class<?> clazz = Class.forName("com.hella.thread.pattern.TaskManager");
        TaskManager instance2 = (TaskManager) clazz.newInstance();
        System.out.println(instance1 == instance2);
    }

}

如何选择单例创建方式

如果不需要延迟加载单例,可以使用枚举或者饿汉式相对来说枚举好于饿汉式

如果需要延迟加载,可以使用静态内部或者韩式相对来说静态内部类好于懒韩式

原文地址:https://www.cnblogs.com/pickKnow/p/9528298.html