【Spring】bean的做用域(@Scope) - singleton、prototype

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

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

  但却知道有大概相似这么一种说法: spring的bean中不容许(或不建议)定义成员变量,不论是public仍是privatespring

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

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

1、测试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在必定状况下能够表示内存)app

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

  这些所有都符合单例的特性。this

  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(...);
        // 一些逻辑
    }
}

2、此文的真正目的

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

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

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

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

  这只记录下二者的差别,主要仍是上面那篇博客中说的。

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

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

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

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

 

  ps: 最近很长一段时间一直至关烦躁,此文章原本很简单,但写到最后我本身都不知道本身到底想表达什么、想记录什么....感受更像应付式的给本身一个任务,形式通常的写一篇文章....

相关文章
相关标签/搜索