线程局部变量。每个访问该变量的线程都有自己独立的初始化副本。ThreadLocal实例通常是类中私有静态字段,将状态与线程(用户ID、事务ID等)想关联。
每个线程内部都有一个ThreadLocalMap
,每个ThreadLocalMap
里面都有一个Entry[]
数组,Entry
对象由ThreadLocal
和数据
组成。
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 从当前线程获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// map不为空设置值
map.set(this, value);
else
// 初始化map
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
获取当前线程,然后根据当前线程获取ThreadLocalMap
。后续操作均针对ThreadLocalMap
。
public T get() {
Thread t = Thread.currentThread();
// 拿到当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 获取值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 获取值时,若map为空则初始化map
return setInitialValue();
}
private T setInitialValue() {
// 初始化null值
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
// 初始化null值
return null;
}
调用ThreadLocalMap.get
方法,获取当前值。
public void remove() {
// 拿到当前Thread的`ThreadLocalMap`对象
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
调用ThreadLocalMap.remove
方法,移除ThreadLocal
。
从以上set
、get
、remove
方法来看,所有操作都是基于该内部类来实现功能的。
// 内部类Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 初始化容量
private static final int INITIAL_CAPACITY = 16;
// Entry数组(开放地址法处理冲突)
private Entry[] table;
// 元素个数
private int size = 0;
// 扩容阈值 table长度*2/3
private int threshold; // Default to 0
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
Entry
的key为ThreadLocal
,并且继承WeakReference
弱引用。ThreadLocalMap
使用开放地址法处理哈希冲突。
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
// 跟ThreadLocal的HashCode和(len-1)进行`与`操作,得到数组索引值。(需确保len为2的倍数)
int i = key.threadLocalHashCode & (len-1);
// 因为使用开放地址法
// 所以需要从当前索引位置开始,不断的向后查找,直到key值相等或者遇到Entry=null值
// 因为有阈值threshold的存在,所以不会形成死循环(肯定存在null值)
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 找到key,更新value
if (k == key) {
e.value = value;
return;
}
// 若key为null,此时Entry不为null,说明ThreadLocal已被回收
if (k == null) {
// 替换陈旧条目
replaceStaleEntry(key, value, i);
return;
}
}
// 生成新Entry
tab[i] = new Entry(key, value);
// size增加
int sz = ++size;
// 清除数据,若没有 并且 size不小于阈值 则进行rehash
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private void rehash() {
// 清除所有已经废弃的节点
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
// 扩容(2倍扩容)
resize();
}
每个ThreadLocal对象都有一个threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加0x61c88647
大小。插入时根据threadLocal对象的hash值,定位哈希表Entry[] table
上的位置:
cleanSomeSlots
清除key为null,entry不为null的Entry,若没有要清除的则判断后续是否要进行扩容private Entry getEntry(ThreadLocal<?> key) {
// 找到Entry[] table中的哈希索引位置
int i = key.threadLocalHashCode & (table.length - 1);
// 尝试查找哈希索引位置是否符合条件
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
// 哈希索引位置不符合条件,尝试开放地址法查找
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 直到Entry为null时,停止查找(说明该key值不存在)
while (e != null) {
ThreadLocal<?> k = e.get();
// 找到key 直接返回
if (k == key)
return e;
// key为null时,此时entry不为null,清除该陈旧entry
if (k == null)
expungeStaleEntry(i);
else
// key值不相等,继续查找下一个
i = nextIndex(i, len);
e = tab[i];
}
// key值不存在,返回null
return null;
}
getEntry大概流程:
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
// 找到索引值
int i = key.threadLocalHashCode & (len-1);
// 开放地址法遍历
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
// Entry清除,key置为null
e.clear();
// 清除key为null,entry不为null的陈旧Entry
expungeStaleEntry(i);
return;
}
}
}
Java中除了原始数据类型的变量,其他的都是引用类型。共有四种引用类型:强引用、软引用、弱引用、虚引用。
被强引用的对象不会被垃圾回收器主动回收,即使抛出OOM异常,使程序终止。
软引用的生命周期比强引用的短一些,只有当JVM认为内存不足时,才会去试图回收软引用指向的对象。JVM会确保在抛出OOM异常前,清理软引用对象。可以和一个引用队列(ReferenceQueue)联合使用。
应用场景:通常用来实现对内存敏感的缓存。还有空闲内存可暂时保留,内存不足时直接清理掉。
弱引用生命周期比软引用更短一些。在垃圾回收器扫描内存时,发现有弱引用对象会直接回收。可以和一个引用队列(ReferenceQueue)联合使用。
应用场景:可用于内存敏感的缓存。
无法通过虚引用来访问对象的任何属性或函数。虚引用仅仅提供了一直确保对象被finalize后,做某些事情的机制。虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾收集器准备回收某个对象时,若发现它还有虚引用,则会在回收对象的内存之前,将这个虚引用加入到与之关联的引用队列中。
应用场景:可用来跟踪对象被垃圾回收器回收的活动。
引用队列可以和软引用、弱引用、虚引用一起配合使用,当垃圾回收器回收一个对象时,若发现它还有引用,就会在回收对象之前将这个引用加入到与之关联的引用队列中去。可以通过判断引用队列中是否已加入了引用,来判断对象是否将要被垃圾回收,就可以在对象被回收之前做一些操作。
严格来说,ThreadLocal
没有内存泄露问题,本身提供了remove
方法来移除。一般来说线程的生命周期比较长,若不主动remove
则不会被释放。在get/set
方法中可以看到,当发现有key==null && entry!=null
的情况时,会主动释放。为了避免出现内存泄露问题,使用完毕后一定要主动调用remove
释放。
强依赖关系:Thread-->ThreadLocalMap-->Entry-->value
SimpleDateFormat
不是线程安全的。主要是因为在SimpleDateFormat
的父类DateFormat
中的Calendar
对象使用int fields[]
来存储当前设置的时间值,并发访问时有可能出现数据异常,故称之为线程不安全。
解决方法有好几个:①将SimpleDateFormat
定义为局部变量,每次调用时创建,调用结束后销毁;②方法加同步锁,避免多线程同时访问;③使用ThreadLocal
,使每个线程都有自己的SimpleDateFormat
。
ThreadLocal<SimpleDateFormat> dateFormatThreadLocal1 = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
ThreadLocal
使用开放地址法解决哈希冲突,数组默认大小16,扩容阈值为2/3,扩容大小为之前的2倍。remove
进行清除数据。ThreadLocal
无法向子线程中传递数据。ThreadLocal
。InheritableThreadLocal
主要解决了ThreadLocal
在父子线程之间无法传值的问题,其实就是无法在子线程中获取父线程的ThreadLocal
。
Thread类
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
// ...
// 当前线程(父线程)
Thread parent = currentThread();
// ...
// 子线程拷贝父线程inheritableThreadLocals
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// ...
}
父线程在调用Thread构造方法生成一个子线程时,构造方法最终会调用Thread#init
方法。如果父线程中存在inheritableThreadLocals
,则将值设置(拷贝)到子线程中。
InheritableThreadLocal类
// 继承自ThreadLocal类
// 获取map时,返回线程的inheritableThreadLocals
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
// 创建map时,赋值给inheritableThreadLocals
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
JDK
的InheritableThreadLocal
类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal
值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal
值传递到 任务执行时。
ThreadLocal
的需求场景即TransmittableThreadLocal
的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的执行组件情况下传递ThreadLocal
值』则是TransmittableThreadLocal
目标场景。