Spring容器中的单例Bean是否线程安全?
有状态对象(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。即每个用户最初都会得到一个初始的bean。
无状态对象(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。
对于单例Bean,所有线程都共享一个单例实例Bean,因此是存在资源的竞争。
如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。
对于有状态的bean,Spring官方提供的bean,一般提供了通过ThreadLocal去解决线程安全的方法;
以下是对ThreadLocal的理解:
每个Thread类中有一个成员变量 threadlocals , 这个成员变量是一个 ThreadLocalMap的类型对象;
我们按住 Ctrl 键鼠标点击 ThreadLocalMap , 进入查看ThreadLocalMap 的底层实现
可以看到一个 Entry[] 数组:

可以看出,每个 Entry 键值对的键是 一个ThreadLocal 对象,值是一个 Object 对象。
这个ThreadLocalMap 类提供了set() 方来设置和添加键值对, 提供了get()方法来获取键值对,提供了remove()方法来删除键值对,看到这些方法,是不是觉得和 ThreadLocal 对象很像,因为ThreadLocal 类也提供了这个三个方法,分别用来存储对象值,获取对象值,以及移除对象值。
综上所述:
ThreadLocal 的set(), get(), remove()方法实际上在操作当前线程成员变量 threadlocals, 这个变量的类型是一个ThreadLocalMap, 所以当我们往ThreadLocal中添加值实际上是把值添加到了当前线程中,从 ThreadLocal 对象中取值实际上是从当前线程中取值,从ThreadLocal 对象中移除值实际上是从把这个值从当前线程中移除,所以一切操作都是在操作当前线程中的值,threadlocal在这里只是相当于一个索引作用;
-------------------------------------->>
ThreadLocal的内存泄漏问题

Entry 继承自WeakReference>,Entry的 key是ThreadLocal对象引用,这个引用是一个弱引用。当没指向 key 的强引用后,该key就会被垃圾收集器回收。
在ThreadLocalMap中,entry的key是弱引用,value仍然是一个强引用。
当某一条线程中的ThreadLocal使用完毕,没有强引用指向它的时候,这个key指向的对象就会被垃圾收集器回收,从而这个key就变成了null;所以entry就变成了(null, value), 而entry 和 value 都是强引用,并且只要entry还在,value就一直存在。所以如果我们不手动清理掉这些键为空的entry, 在线程执行完毕之前,这个entry就一直处于内存泄漏的状态。线程生命周期越长,内存泄漏的就越多。
解决办法:
hreadLocal提供了这个问题的解决方案。
(1)每次操作set、get、remove操作时,会相应调用 ThreadLocalMap 的三个方法,ThreadLocalMap的三个方法在每次被调用时 都会直接或间接调用一个expungeStaleEntry() 方法,这个方法会将key为null的 Entry 删除,从而避免内存泄漏
(2)如果一个线程运行周期比较长,而且将一个大对象放在LocalThreadMap后便不再调用set、get、remove方法仍然有可能key的弱引用被回收后,引用没有被回收,此时该仍然可能会导致内存泄漏。
所以需要程序员在使用ThreadLocal之后手动调用move删除;
既然弱引用会导致内存泄漏,那ThreadLocalMap为什么对ThreadLocal的引用要设置成弱引用?
为了尽快回收这个线程变量,因为这个线程变量可能使用场景不是特别多,所以希望使用完后能尽快被释放掉。因为线程拥有的资源越多,就越臃肿,线程切换的开销就越大,所以希望尽量降低线程拥有的资源量
网友评论