背景
spring默认bean的注册形式是单例模式。那spring是如何解决并发安全问题的呢?就是通过ThreadLocal。到底ThreadLocal有和“魔力”能让普通类变成线程安全的类呢?
原理
先来看看ThreadLocal.java的源码注释:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID).
大致意思:
这个类提供线程局部变量。这种在多线程环境下访问(通过get或set方法)时,能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal实例通常是
private static
类型的,用于管理线程上下文。
也就是说,Threadlocal提供了作用范围为线程的局部变量,这种变量只在线程生命周期内起作用。减少了线程内多个方法之间公共变量传递的复杂度。
这里关于线程安全的类有一个普遍适用的原则:如果一个类没有实例私有属性,或者实例私有属性也是无状态的类,那么这个类就是无状态的类。而一个无状态的类肯定是线程安全的类。
而用ThreadLocal包装类的所有实例私有属性后,这个类就没有实例私有属性了,那么这个类就是一个无状态类,因此也是一个线程安全的类。这也是spring使用ThreeadLocal处理bean的默认方式。
ThreadLocal基本使用
先来看看ThreadLocal几个常用方法。
构造函数
|
|
内部没有任何实现。
initialValue方法
|
|
initialValue()
用来设置ThreadLocal的初始值。方法是protected
的,建议在子类中被重载,以指定初始值。通常使用匿名内部类的形式。例如以下代码所示:
|
|
withInitial
withInitial()
也是用来初始化的,但是是lamda风格的初始化方法。构造方法中也是推荐使用此方法。例如以下代码所示:
|
|
使用测试代码
|
|
线程{10}的初始值为:0
线程{12}的初始值为:2
线程{11}的初始值为:1
线程{11}的累加值为:101
线程{12}的累加值为:102
线程{10}的累加值为:100
由此可以看到,各个线程的threadlocal值是独立的。本线程对threadlocal中值的改动并没有影响到其他线程。
ThreadLocal的实现原理
先查看ThreadLocal的get()方法源码:
|
|
其中getMap的源码:
|
|
setInitialValue的源码:
|
|
createMap的源码:
|
|
查看以上源码后,我们可以了解get方法的流程:
1.首先获取当前线程
2.获取当前线程中的一个类型为ThreadLocalMap(这个类后面会讲到)的成员变量:threadLocals
3.如果threadLocalMap不为null,这通过当前ThreadLocal的引用作为key获取对应的value e。同时如果e不为null,返回e.value
4.如果threadLocalMap为null或者e为null,通过``setInitialValue``方法返回初始值。并且使用当前ThreadLocal的引用和value作为初始key与value创建一个新的threadLocalMap
总体设计思路:Thread维护了一个Map,key为ThreadLocal实例本身,value为真正需要存储的Object。
这样设计的好处:Map的Entry数量变小,性能提升。并且会随Thread一起销毁。
ThreadLocalMap解析
先查看源码:
|
|
ThreadLocalMap是ThreadLocal的一个静态内部类。类上注释也解释了其基本实现方式:
ThreadLocalMap是一个自定义的hash map,只适合用来维护现场局部变量。并且是包级私有。hash表中的key是一个
ThreadLocal
的弱引用。当没有对ThreadLocal
的强引用,并且发生GC时,该Entry必然会被回收。
这里的弱引用也保证了不会因为线程迟迟没有结束,而ThreadLocal的强引用不存在了,保存在ThreadLocalMap中的Entry却还依然存在。