1.简述ThreadLocal
ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程。ThreadLocal是一个线程级别的局部变量,下面是线程局部变量(ThreadLocal variables)的关键点:
A、当使用ThreadLocal维护变量时,若多个线程访问ThreadLocal实例,ThreadLocal为每个使用该变量的线程提供了一个独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。
B、从线程的角度看,目标变量就像是线程的本地变量,这也是类名中Local所要表达的意思。
2.细看ThreadLocal
ThreadLocal类很简单,只有四个方法:
(1)void set(T value),该方法用来设置当前线程中变量的副本
(2)public T get(),该方法是用来获取ThreadLocal在当前线程中保存的变量副本
(3)public void remove(),该方法用来移除当前线程中变量的副本,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束以后,对应线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
(4)protected T initialValue(),该方法是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,ThreadLocal中的缺省实现直接返回一个null。
3.ThreadLocal示例
简单的使用方法如下:
packagecom.test;public classThreadMain {//①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal seqNum = new ThreadLocal() {publicInteger initialValue() {return 0;
}
};//②获取下一个序列值
public intgetNextNum() {
seqNum.set(seqNum.get()+ 1);returnseqNum.get();
}public static voidmain(String[] args) {
ThreadMain sn= newThreadMain();//③ 3个线程共享sn,各自产生序列号
TestClient t1 = newTestClient(sn);
TestClient t2= newTestClient(sn);
TestClient t3= newTestClient(sn);
t1.start();
t2.start();
t3.start();
}private static class TestClient extendsThread {privateThreadMain sn;publicTestClient(ThreadMain sn) {this.sn =sn;
}public voidrun() {for (int i = 0; i < 3; i++) {//④每个线程打出3个序列值
System.out.println("thread[" +Thread.currentThread().getName()+ "] --> sn[" + sn.getNextNum() + "]");
}
}
}
}
结果如下:
thread[Thread-0] --> sn[1]
thread[Thread-2] --> sn[1]
thread[Thread-1] --> sn[1]
thread[Thread-2] --> sn[2]
thread[Thread-0] --> sn[2]
thread[Thread-2] --> sn[3]
thread[Thread-1] --> sn[2]
thread[Thread-1] --> sn[3]
thread[Thread-0] --> sn[3]
另一个案例
packagecom.csu.thread;classGlobalVarManager {private static ThreadLocal globalVars = new ThreadLocal(){protectedString initialValue() {return "hello";
}
};public static ThreadLocalgetglobalVars() {returnglobalVars;
}
}class ThreadRun implementsRunnable {private ThreadLocalt;privateString str;
ThreadRun(ThreadLocaltemp, String s) {this.t =temp;this.str =s;
}
@Overridepublic voidrun() {
System.out.println(Thread.currentThread()+"改变前:" +t.get());
t.set(str);
System.out.println(Thread.currentThread()+"改变后:" +t.get());
}
}public classThreadLocalTry {public static voidmain(String[] args) {for (int i =1; i < 5; i++) {new Thread(new ThreadRun(GlobalVarManager.getglobalVars(), ""+i)).start();
}
}
}
结果如下:
Thread[Thread-0,5,main]改变前:hello
Thread[Thread-1,5,main]改变前:hello
Thread[Thread-0,5,main]改变后:1Thread[Thread-2,5,main]改变前:hello
Thread[Thread-1,5,main]改变后:2Thread[Thread-2,5,main]改变后:3Thread[Thread-3,5,main]改变前:hello
Thread[Thread-3,5,main]改变后:4
上述案例也可按如下方式来实现:
packagecom.csu.test;classGlobalVarManager {private static ThreadLocal globalVars = new ThreadLocal(){protectedString initialValue() {return "hello";
}
};public staticString getGlobalVars() {returnglobalVars.get();
}public static voidsetGlobalVars(String str) {
globalVars.set(str);
}
}class ThreadRun implementsRunnable {private String str = null;publicThreadRun(String temp) {
str=temp;
}
@Overridepublic voidrun() {
System.out.println(Thread.currentThread()+"改变前:" +GlobalVarManager.getGlobalVars());
GlobalVarManager.setGlobalVars(str);
System.out.println(Thread.currentThread()+"改变后:" +GlobalVarManager.getGlobalVars());
}
}public classThreadLocalTest {public static voidmain(String[] args) {for (int i = 1; i < 5; i++) {new Thread(new ThreadRun("" +i)).start();
}
}
}
结果如下:
Thread[Thread-3,5,main]改变前:hello
Thread[Thread-2,5,main]改变前:hello
Thread[Thread-1,5,main]改变前:hello
Thread[Thread-0,5,main]改变前:hello
Thread[Thread-1,5,main]改变后:2Thread[Thread-2,5,main]改变后:3Thread[Thread-3,5,main]改变后:4Thread[Thread-0,5,main]改变后:1
4.ThreadLocal的实现机制
此部分内容暂没有深入研究,欲了解更多内容请参考https://www.cnblogs.com/dennyzhangdd/p/7978455.html
(1)get()方法源码如下:
(2)set()方法源码如下:
(3)remove()方法源码如下:
(4)上述几个函数涉及到如下两个函数
从前述源码可以看出,ThreadLocal的get、set、remove方法都是操作当前线程,而从Thread的源码可以看出该类有一个ThreadLocal.ThreadLocalMap类型的变量threadLocals,该变量在初次调用ThreadLocal的set()方法时通过createMap()方法初始化
5.ThreadLocalMap
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 classThreadLocalMap {/*** 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>{/**The value associated with this ThreadLocal.*/Object value;
Entry(ThreadLocal>k, Object v) {super(k);
value=v;
}
}/*** The initial capacity -- MUST be a power of two.*/
private static final int INITIAL_CAPACITY = 16;/*** The table, resized as necessary.
* table.length MUST always be a power of two.*/
privateEntry[] table;/*** The number of entries in the table.*/
private int size = 0;/*** The next size value at which to resize.*/
private int threshold; //Default to 0
/*** Set the resize threshold to maintain at worst a 2/3 load factor.*/
private void setThreshold(intlen) {
threshold= len * 2 / 3;
}/*** Increment i modulo len.*/
private static int nextIndex(int i, intlen) {return ((i + 1 < len) ? i + 1 : 0);
}/*** Decrement i modulo len.*/
private static int prevIndex(int i, intlen) {return ((i - 1 >= 0) ? i - 1 : len - 1);
}/*** Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.*/ThreadLocalMap(ThreadLocal>firstKey, Object firstValue) {
table= newEntry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i]= newEntry(firstKey, firstValue);
size= 1;
setThreshold(INITIAL_CAPACITY);
}/*** Construct a new map including all Inheritable ThreadLocals
* from given parent map. Called only by createInheritedMap.
*
*@paramparentMap the map associated with parent thread.*/
privateThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable=parentMap.table;int len =parentTable.length;
setThreshold(len);
table= newEntry[len];for (int j = 0; j < len; j++) {
Entry e=parentTable[j];if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal key = (ThreadLocal) e.get();if (key != null) {
Object value=key.childValue(e.value);
Entry c= newEntry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)
h=nextIndex(h, len);
table[h]=c;
size++;
}
}
}
}
此处重点关注一下ThreadLocalMap中的几个成员变量及方法
(1)private Entry[] table;
table是一个Entry类型的数组,该变量在ThreadLocalMap的构造函数中初始化
Entry是ThreadLocalMap的一个内部类
(2)set()方法
/*** Set the value associated with key.
*
*@paramkey the thread local object
*@paramvalue 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]= newEntry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >=threshold)
rehash();
}
(3)getEntry()方法
/*** Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
*@paramkey the thread local object
*@returnthe entry associated with key, or null if no such*/
private Entry getEntry(ThreadLocal>key) {int i = key.threadLocalHashCode & (table.length - 1);
Entry e=table[i];if (e != null && e.get() ==key)returne;else
returngetEntryAfterMiss(key, i, e);
}/*** Version of getEntry method for use when key is not found in
* its direct hash slot.
*
*@paramkey the thread local object
*@parami the table index for key's hash code
*@parame the entry at table[i]
*@returnthe entry associated with key, or null if no such*/
private Entry getEntryAfterMiss(ThreadLocal> key, inti, Entry e) {
Entry[] tab=table;int len =tab.length;while (e != null) {
ThreadLocal> k =e.get();if (k ==key)returne;if (k == null)
expungeStaleEntry(i);elsei=nextIndex(i, len);
e=tab[i];
}return null;
}
(4)remove()方法
/*** Remove the entry for key.*/
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) {
e.clear();
expungeStaleEntry(i);return;
}
}
}
6.总结
ThreadLocal一般都是声明在静态变量中,如果不断地创建ThreadLocal而没有调用其remove方法,将导致内存泄露,特别是在高并发的Web容器当中。
ThreadLocal在处理线程的局部变量时比synchronized同步机制解决线程安全问题更简单,更方便,且程序拥有更高的并发性。