文章目录
1、ThreadLocal是什么
ThreadLocal是多线程环境下,为了变量安全提供的一种解决方案。ThreadLocal提供了线程的局部变量,每个线程都可以通过set()和get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程之间的数据隔离。
往ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadLocalTest {
public static ThreadLocal<String> localVar = new ThreadLocal<>();
public static void print() {
//获取当前线程中本地变量的值
String before = localVar.get();
//清除当前线程中本地变量的值
localVar.remove();
//再次获取当前线程中本地变量的值
String after = localVar.get();
//打印
System.out.println("before remove method var:[" + before + "] after remove method var:[" + after + "]");
}
public static void main(String[] args) {
//定义一个线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(20, 20, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));
for (int i = 0; i < 20; i++) {
threadPoolExecutor.execute(() -> {
//设置线程中本地变量的值
localVar.set("var=" + Thread.currentThread().getId());
//调用打印方法
print();
});
}
threadPoolExecutor.shutdown();
}
}
上面这段代码就可以表现出ThreadLocal变量变量线程隔离的特点。
2、ThreadLocal可以做什么
2.1、避免一些参数传递
想象下同一线程顺序执行下来,方法的层级调用关系很深。但是很多地方都需要一个共同变量A,该如何解决?
我们可以通过参数传递一层一层向下传递,某些中间方法即便不需要该变量A,也依然要帮忙把变量传递下去。这样我们的代码结构就显得非常臃肿和难看了。
而如果采取ThreadLocal线程变量的方式,则可以避免这样的参数传递,可以通过ThreadLocal很方便的设置和获取需要的变量。
2.2、管理Connection
经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关闭了 B线程正在使用的 Connection; 还有 Session 管理等问题。实际就是利用ThreadLocal线程变量的隔离性,独占某个资源。
3、ThreadLocal实现的原理
本文使用的是JDK 1.8
3.1、ThreadLocal的set()方法
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();//当前线程对象
ThreadLocalMap map = getMap(t);//通过当前Thread获得一个ThreadLocalMap对象
if (map != null)
//如果不为空,则设置值。key为当前定义的ThreadLocal变量的this引用
map.set(this, value);
else
//如果为空,则新建,并且设置当前value
createMap(t, value);
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- set(T value)的实现看起来挺简单,主要是通过一个ThreadLocalMap来进行操作的。
- ThreadLocalMap的持有者是Thread t线程对象,所以说变量线程的隔离性就是这么来的。
- ThreadLocal类就像是一个代理,各种操作实际都是操作当前Thread t线程对象的ThreadLocalMap threadLocals局部变量。
- 可以说搞清楚了ThreadLocalMap就了解了ThreadLocal是怎么回事了。
3.2、ThreadLocalMap类深入解读
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
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;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
ThreadLocalMap定义在ThreadLocal中的一个静态内部类,但对象的引用却是在Thread中
ThreadLocalMap内部使用的是Entry[] table来保存线程变量的,对于Entry来说key是当前ThreadLocal对象,value是传递进来的对象
3.2.1、Entry详细介绍
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
我们看到这个Entry类的定义,正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用。
弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以ThreadLocal设定的value值被持有,导致内存泄露。按照道理一个线程使用完,ThreadLocalMap是应该要被清空的,但是现在线程被复用了。
解决方法,就是在使用完ThreadLocal后,调用remove方法
ThreadLocal<String> localVar= new ThreadLocal();
try {
localVar.set("张三");
……
} finally {
localVar.remove();
}
3.2.2、Entry[] table的存取逻辑
- 根据ThreadLocal对象的hash值,定位到table中的位置 i,int i = key.threadLocalHashCode & (len-1)
- 如果位置 i 不为空,如果这个Entry对象的key正好是即将设置的key,那么就覆盖Entry中的value
- 如果当前位置是空的,就初始化一个Entry对象放在位置 i 上
- 如果位置 i 不为空,而且key不等于entry,那就找下一个空位置,直到为空为止
3.3、ThreadLocal的get()方法
理解了ThreadLocal的set()方法和ThreadLocalMap类之后,就很容易理解get()方法了
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//先获得当前线程Thread的ThreadLocalMap对象
if (map != null) {
//根据当前ThreadLocal对象作为Key来获得Entry对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;//Entry对象的value
return result;
}
}
return setInitialValue();
}
3.4、ThreadLocal原理总结
- 每个Thread维护着一个ThreadLocalMap的引用,ThreadLocalMap是ThreadLocal的内部类,用Entry来存储键值对,用Entry[] table数组来存储同一线程中多个ThreadLocal对象的键值对。
- 调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是当前ThreadLocal对象,值是传递进来的对象。
- 调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是当前ThreadLocal对象。
- ThreadLocal本身并不存储值,它只是作为一个key,让线程从ThreadLocalMap中获取对应的value。
4、弱引用
- threadlocal value为什么不是弱引用?
关于key设计成弱引用的理解,总结起来就是:既然ThreadLocal不让开发者关心key的处理,那么它自己就应该负责key的管理;
value作为开发者外部传入的值,那么value的管理应该由开发者自己负责。
假如value被设计成弱引用,那么很有可能当你需要取这个value值的时候,取出来的值是一个null。你使用ThreadLocal的目的就是要把这个value存储到当前线程中,并且希望在你需要的时候直接从当前线程中拿出来,那么意味着你的value除了当前线程对它持有强引用外,理论上来说,不应该再有其他强引用,否则你也不会把value存储进当前线程。但是一旦你把本应该强引用的value设计成了弱引用,那么只要jvm执行一次gc操作,你的value就直接被回收掉了。当你需要从当前线程中取值的时候,最终得到的就是null。