【开源框架】spring IOC 容器中的单例,高并发情况下成员变量不被影响原因及原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/seesun2012/article/details/84719960

问题分析:

  • 众所周知,SpringMVC 中的 Controller 是存放在IOC容器中的,由于 SpringICO 中存放的都是单例模式对象,当多个请求瞬间同时从 Servlet 进入 Controller 时就会产生全局变量被修改的线程安全问题(spring已经解决了这个问题,这里分析的是怎么解决)。

spring的解决方案:

  • 创建一个副本去执行(重新实例化),避免多个线程对一个全局变量产生修改(在堆内存建立一个独立对象)

  • 只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享

1、模拟SpringICO测试案例:

package com.seesun2012.spring.test;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @title:     SpringICO测试类
 * @version    v1.0.0
 * @author     csdn:seesun2012
 * @date       2018年12月02日  下午15:51:44  周日
 *
 */
public class SpringICO {

    // 全局变量  (这里不可为静态变量,新手坑误踩,大神请忽略)
    private Integer ssa = 0;
    //  设定ICO容器,将实例化的对象缓存进IOC中
    private static Map<String, Object> ioc = new HashMap<String, Object>();
    // 设定请求映射器
    private static Map<String, Method> handleMapping = new HashMap<String, Method>();
    // 定义维持5个数量的线程池
    private static final ExecutorService service = Executors.newFixedThreadPool(5);

    /**
     * IOC初始化(例如servlet初始化)
     */
    public static void init() throws Exception{
        //  获取当前类,也可以做成扫描 @Controller注解 方式获取(注解+反射)
        Class clazz = new SpringICO().getClass();
        //  将路径为 SpringICO/doGet 对象以单例的方式注入到IOC容器
        ioc.put(clazz.getSimpleName(), clazz.newInstance());
        //  获取对象的方法,也可以做成扫描 @RequestMapping注解 方式获取(注解+反射)
        Method method = clazz.getMethod("doGet", String.class);
        //  将对象方法名称与方法进行关系映射
        handleMapping.put(clazz.getSimpleName() + "/doGet", method);
    }

    /**
     * 请求分发
     * @param url       请求路径
     * @param params    请求参数
     */
    public static Object service(String url, String params) {
        Object str = "线程" + Thread.currentThread().getName() + ",输出参数:";
        try {
            //  解析请求路径
            String[] urlArr = url.split("/");
            //  通过动态代理执行请求过来的方法(执行前会创建一个副本)
            Object retr = handleMapping.get(url).invoke(ioc.get(urlArr[0]).getClass().newInstance(), params);
            str = str + retr.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return str;
    }

    /**
     * 请求执行器
     */
    public Integer doGet(String str) {
        //  设定每次进入的请求都将全局变量ssa+1(问题所在)
        return ++ssa;
    }

    public static void main(String[] args) throws Exception{
        //  启动初始化
        init();
        //  打印请求路径
        for (Map.Entry<String, Method> entry: handleMapping.entrySet()) {
            System.out.println("init() INFO URL:--------------" + entry.getKey() + "--------------");
        }
        //  设定多线程同时并发访问
        for(int i = 0 ; i < 10 ; i++){
            //  提交线程到线程池
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(service("SpringICO/doGet", ""));
                }
            });
        }
        //  线程池计数,当service.submit()到最后一个线程时启动执行,即0
        service.shutdown();
    }

}

【创建副本】解决方案:
关键代码,在于Class.newInstance()

Object retr = handleMapping.get(url).invoke(ioc.get(urlArr[0]).getClass().newInstance(), params);

测试结果:

init() INFO URL:--------------SpringICO/doGet--------------
线程pool-1-thread-2,输出参数:1
线程pool-1-thread-3,输出参数:1
线程pool-1-thread-1,输出参数:1
线程pool-1-thread-2,输出参数:1
线程pool-1-thread-3,输出参数:1
线程pool-1-thread-5,输出参数:1
线程pool-1-thread-4,输出参数:1
线程pool-1-thread-3,输出参数:1
线程pool-1-thread-2,输出参数:1
线程pool-1-thread-1,输出参数:1

【无副本】测试:
关键代码,将54行代码修改为:

Object retr = handleMapping.get(url).invoke(ioc.get(urlArr[0]), params);

测试结果:

init() INFO URL:--------------SpringICO/doGet--------------
线程pool-1-thread-3,输出参数:1
线程pool-1-thread-5,输出参数:2
线程pool-1-thread-3,输出参数:3
线程pool-1-thread-5,输出参数:4
线程pool-1-thread-3,输出参数:5
线程pool-1-thread-5,输出参数:6
线程pool-1-thread-3,输出参数:7
线程pool-1-thread-4,输出参数:8
线程pool-1-thread-1,输出参数:8
线程pool-1-thread-2,输出参数:9



持续更新中…

如有对思路不清晰或有更好的解决思路,欢迎与本人交流,QQ群:273557553,个人微信:seesun2012

扫描二维码关注公众号,回复: 4353791 查看本文章

你的提问是小编创作灵感的来源!
































猜你喜欢

转载自blog.csdn.net/seesun2012/article/details/84719960
今日推荐