ThreadLocal是JDK提供的一个工具类,其作用是在多线程共享资源的情况下,使每个线程持有一份该资源的副本,每个线程的副本都是独立互不影响的。线程操作各自的副本,这样就避免了资源竞争引发的线程安全问题。
模拟Spring中的事务管理,每个事务与当前线程绑定,不同线程的事务之间相互独立互不影响。代码如下:
public class TransactionManager {
//业务线程
private static final class BizTask implements Runnable{
private final ThreadLocal<Transaction> transaction;
public BizTask(){
//创建事务,并与当前线程绑定
transaction = ThreadLocal.withInitial(() -> {
long id = Thread.currentThread().getId();
return new Transaction(id);
});
}
@Override
public void run() {
transaction.get().begin();
System.err.println("执行业务逻辑");
try {
Thread.sleep(1000L);
transaction.get().commit();
transaction.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//事务
private static final class Transaction {
private long id;
private TransactionStatus status;
public Transaction(long id) {
this.id = id;
}
public void begin(){
status = TransactionStatus.BEGIN;
System.err.println("开启事务, id: " + id + ", status: " + status);
}
public void commit() {
status = TransactionStatus.COMMIT;
System.err.println("提交事务, id: " + id + ", status: " + status);
}
public void rollback() {
status = TransactionStatus.ROLLBACK;
System.err.println("回滚事务, id: " + id + ", status: " + status);
}
}
//事务状态
private enum TransactionStatus {
BEGIN,
COMMIT,
ROLLBACK,
}
public static void main(String[] args) throws InterruptedException {
int threadNum = 5;
ExecutorService executor = Executors.newFixedThreadPool(threadNum);
//执行业务逻辑,可以看到每个线程的事务相互独立,互不影响
for (int i = 0; i < threadNum; i++) {
executor.submit(new BizTask());
}
executor.shutdown();
}
}
结果如下:
开启事务, id: 17, status: BEGIN
执行业务逻辑
开启事务, id: 14, status: BEGIN
执行业务逻辑
开启事务, id: 13, status: BEGIN
执行业务逻辑
开启事务, id: 16, status: BEGIN
执行业务逻辑
开启事务, id: 15, status: BEGIN
执行业务逻辑
提交事务, id: 14, status: COMMIT
提交事务, id: 17, status: COMMIT
提交事务, id: 13, status: COMMIT
提交事务, id: 16, status: COMMIT
提交事务, id: 15, status: COMMIT
ThreadLocal,线程本地变量,该变量为每个线程私有。ThreadLocal类有一个内部类,名为ThreadLocalMap,可以理解为一个简化版的HashMap。源码如下:
static class ThreadLocalMap {
//该Map的Entry,Key为ThreadLocal实例,Value为ThreadLocal对象所引用的值。
//这里使用了WeakReference弱引用,当Entry为null时可以尽快被GC
static class Entry extends WeakReference<ThreadLocal<?>> {
//与ThreadLocal关联的对象引用,为当前Entry的value
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//初始容量16
private static final int INITIAL_CAPACITY = 16;
//使用数组保存了所有的线程本地变量
private Entry[] table;
}
其中Entry为ThreadLocalMap的一个内部类,与HashMap的Entry结构类似,都是key-value对的形式。它的Key为ThreadLocal实例,Value为ThreadLocal对象所引用的对象。ThreadLocalMap使用一个Entry[]数组保存了所有的线程本地变量,因为一个线程可以维护多个ThreadLocal实例。
ThreadLocalMap内部保存了众多的ThreadLocal对象,既然说ThreadLocal是线程私有的,那么ThreadLocalMap是存放在哪里呢?
Thread类有一个成员变量——threadLocals,它就是保存了与当前Thread关联的一个ThreadLocalMap,源码如下:
//当前线程内部维护的ThreadLocalMap对象,用于保存所有ThreadLocal实例
ThreadLocal.ThreadLocalMap threadLocals = null;
可以看到,ThreadLocalMap对象保存在了Thread的内部,也即当前线程的私有内存中。通过上面的分析,我们可以了解ThreadLocal变量的大致内存结构如下:
ThreadLocal的主要方法为get()、set()和initialValue()。首先看set():
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程关联的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//创建一个Entry,以当前ThreadLocal对象为Key,待存储对象为Value,保存在ThreadLocalMap中
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
可以看到,set()的逻辑很简单,从当前线程中获取ThreadLocalMap,然后将该ThreadLocal的值保存在里面。
再看get()方法:
public T get() {
//获取当前线程对象
Thread t = Thread.currentThread();
//获取当前线程关联的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//从ThreadLocalMap获取以ThreadLocal为key的Entry的value
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果当前ThreadLocalMap不存在,则调用setInitialValue()方法,获取初始值
return setInitialValue();
}
ThreadLocal还有一个方法initialValue(),该方法提供给子类覆盖,以在创建ThreadLocal时指定初始值。
ThreadLocal最常见的使用场景为管理数据库连接Connection对象等。Spring中使用ThreadLocal来设计TransactionSynchronizationManager类,实现了事务管理与数据访问服务的解耦,同时也保证了多线程环境下connection的线程安全问题。
ThreadLocal之所以设计成WeakReference,目的就是当外部不再持有ThreadLocal的强引用时,尽快回收该ThreadLocalMap中对应的key。
public T get() {
//获取当前线程实例
Thread t = Thread.currentThread();
//获取当前线程中的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//通过ThreadLocalMap的getEntry()方法获取Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
//如果Entry获取不到,则调用getEntryAfterMiss()
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
//如果key为null,则清除该Entry项
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
Entry的key使用WeakReference弱引用,来处理大内存、长生命周期的线程的使用问题。
由此可以看出,无论是否使用WeakReference,都有可能产生内存泄漏的情况,其根本原因在于ThreadLocalMap的生命周期与线程绑定。如果线程存活时间较长,且没有显式remove掉ThreadLocal对象,就有可能出现问题。而使用了WeakReference,至少可以保证无用的ThreadLocal对象被回收,不会出现整个Entry的内存泄漏,在一定程度上缓解了该问题。