• 【Spring】bean的作用域(@Scope)


      已知spring 3+已拥有多种不同的作用域: singleton(默认)、prototype、request、session、global session。(参考: spring中scope作用域(转))

      到目前为止,其实还没在项目中实际遇到要修改作用域的情况。

      但却知道有大概类似这么一种说法: spring的bean中不允许(或不建议)定义成员变量,不管是public还是private

      但之前在做一个功能的时候确实遇到了想在service定义一个成员变量Map类型的,但有映像spring中默认是单例,结合单例的特性。考虑到可能定义成员变量有问题,所以就重新回来看一下。

      (最后也没采用定义成员变量的方式,还是用的参数传递。) 

    一、测试singleton、prototype的差异

      1.1 singleton主要测试代码
    @Controller
    @Scope("singleton")
    public class SingletonController {
        @Autowired
        private SingletonService singletonService;
        private Integer controllerIndex = 1;
    
        @RequestMapping("/singleton")
        @ResponseBody
        public Map<String, Object> singleton(){
            Map<String, Object> rs = new HashMap<>();
            rs.put("service_index",singletonService.getIndex());
            rs.put("controller_index",controllerIndex);
            rs.put("controller_hashCode",this.hashCode());
            rs.put("service_hashCode",singletonService.hashCode());
            rs.put("cache",singletonService.getCache());
            return rs;
        }
    }
    @Service
    @Scope("singleton")
    public class SingletonService {
        private Map<String,Object> cache = new HashMap<>();
        private Integer index = 1;
    
        public Map<String, Object> getCache() {
            return cache;
        }
    
        public Integer getIndex() {
            cache.put("index-"+index,index);
            return index++;
        }
    }

      结果猜想:

        1) 每次请求后controller_index、service_index都在递增。

        2) controller_hashCode、service_hashCode每次都保持不变,因为单例只有一个实例。(java中得不到内存地址,变相的hashCode在一定情况下可以表示内存)

        3) cache的key/value一直在增多,请求一次多一个。

      这些全部都符合单例的特性。

      1.2 prototype主要测试代码
    @Controller
    @Scope("prototype")
    public class PrototypeController {
        @Autowired
        private PrototypeService prototypeService;
        private Integer controllerIndex = 1;
    
        @RequestMapping("/prototype")
        @ResponseBody
        public Map<String, Object> singleton(){
            Map<String, Object> rs = new HashMap<>();
            rs.put("service_index",prototypeService.getIndex());
            rs.put("controller_index",controllerIndex);
            rs.put("controller_hashCode",this.hashCode());
            rs.put("service_hashCode",prototypeService.hashCode());
            rs.put("cache",prototypeService.getCache());
            return rs;
        }
    }
    @Service
    @Scope("prototype")
    public class PrototypeService {
        private Map<String,Object> cache = new HashMap<>();
        private Integer index = 1;
    
        public Map<String, Object> getCache() {
            return cache;
        }
    
        public Integer getIndex() {
            cache.put("index-"+index,index);
            return index++;
        }
    }

      结果猜想:

        1) controller_index、service_index始终都是1。

        2) controller_hashCode、service_hashCode每次都在改变。

        3) cache只有一个key/value,即{"index-1": 1}。

      1.3 结论

        实际的结果和猜想完全符合,就是简单的单例/多例的区别。

        所以如果存在类似的代码:

    @Service
    @Scope("singleton")
    public class SingletonService {
        private Map<String,Object> cache = null;
    
        public void aMethod() {
            cache = new HashMap<>();
            // 一些逻辑
            cache.put(...);
            bMethod();
        }
    
        public void bMethod() {
            Object obj = cache.get(...);
            // 一些逻辑
        }
    }
    

        想现在aMethod()中处理一些逻辑,然后把符合的保存起来供bMethod()使用。并且你简单的测试不会有问题,因为是单线程的测试。

        但现在整体来看,如果多个用户同时操作。本来A是put(1,"a"),但是此时B又紧接着put(1,"b")。A先进入bMethod(),那么get(1) = "b",这就是明显的多线程并发问题。

        当然可以把Scope改成prototype,但现在的处境是: 1) 没绝对的比较。2) 整个项目中并没有用prototype的先例。

        所以最常见的作法是改成方法传参:

    @Service
    @Scope("singleton")
    public class SingletonService {
    
        public void aMethod() {
            private Map<String,Object> cache = new HashMap<>();
            // 一些逻辑
            cache.put(...);
            bMethod();
        }
    
        public void bMethod(Map<String,Object> cache) {
            Object obj = cache.get(...);
            // 一些逻辑
        }
    }

    二、此文的真正目的

      简单目的: 在spring默认情况下的bean中定义成员变量带来的风险。

      但,其实是记录spring是怎么解决线程安全的。(详见: Spring单例与线程安全小结)

      我个人对线程也不是足够了解,去零零碎碎看过,但实际项目中确实还没完全真正的结果过。

      但在以前看过过一篇文章(就是上面那篇),写spring是怎么解决线程安全的,写spring利用的不是线程同步机制(synchronized)而是用的ThreadLocal。

      这只记录下两者的差异,主要还是上面那篇博客中说的。

      synchronized: 时间换空间,以较慢的执行时间来节约空间的被占用。

    在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

      ThreadLocal: 空间换时间,占用更多的空间来换取较快的执行时间。

    在ThreadLocal中,会为每一个线程提供一个独立的变量副,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

     

      ps: 最近很长一段时间一直相当烦躁,此文章本来很简单,但写到最后我自己都不知道自己到底想表达什么、想记录什么....感觉更像应付式的给自己一个任务,形式一般的写一篇文章....

  • 相关阅读:
    狼羊过河问题
    java实现透明窗体
    商人胡萝卜问题
    NXP迅为IMX8开发板Andaoid编译环境搭建
    迅为龙芯2K1000开发板Linux下gcc编译
    迅为恩智浦i.MX8MM开发平台虚拟机安装Ubuntu16.04系统
    恩智浦NXP迅为i.MX6Q开发板资料提升了
    迅为龙芯2K1000开发板Linux工具之make工具和Makefile文件
    迅为瑞芯微3399开发板minimalYocto文件系统的构建
    迅为恩智浦IMX6Q开发板Buildroot 文件系统 alsa 声卡工具测试
  • 原文地址:https://www.cnblogs.com/VergiLyn/p/6965571.html
Copyright © 2020-2023  润新知