• 单例模式


    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
Copyright © 2020-2023  润新知