• 单例模式


    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);
        }
    
    }

    如何选择单例创建方式

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

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

  • 相关阅读:
    进程与线程
    the art of seo(chapter seven)
    the art of seo(chapter six)
    the art of seo(chapter five)
    the art of seo(chapter four)
    the art of seo(chapter three)
    the art of seo(chapter two)
    the art of seo(chapter one)
    Sentinel Cluster流程分析
    Sentinel Core流程分析
  • 原文地址:https://www.cnblogs.com/pickKnow/p/9528298.html
Copyright © 2020-2023  润新知