Java面試必問ThreadLocal捌浩,所以想必大家對ThreadLocal并不陌生右犹,從字面意思來看ThreadLocal很容易理解,但是想要真正理解并沒有那么容易,今天我們就來扒下它的外衣……
1.首先我們來看看ThreadLocal如何使用掉分,它能解決什么樣的問題
ThreadLocal赴恨,線程本地變量晤郑,它可以為變量在每個(gè)線程中都創(chuàng)建一個(gè)副本负蚊,但它本身能夠被多個(gè)線程共享使用,并且又能夠達(dá)到線程安全的目的浆兰,且絕對線程安全磕仅。
來來來珊豹,F(xiàn)or example:
ThreadLocal<String> strLocal1=new ThreadLocal<>();
public void setValue(){
strLocal1.set(Thread.currentThread().getName());
}
public String getStrLocal1(){
return strLocal1.get();
}
public void Run(){
setValue();
System.out.println("<<<<"+Thread.currentThread().getName()+":"+getStrLocal1());
Thread thread=new Thread(){
@Override
public void run() {
super.run();
setValue();
System.out.println("<<<<"+Thread.currentThread().getName()+":"+getStrLocal1());
}
};
thread.start();
System.out.println("<<<<"+Thread.currentThread().getName()+":"+getStrLocal1());
}
看看Log:
<<<<main:main
<<<<main:main
<<<<Thread-4:Thread-4
從這段代碼的輸出結(jié)果可以看出,在main
線程中和Thread-4
線程中榕订,strLocal1
保存的副本值都不一樣平夜。最后一次在main
線程再次打印副本值是為了證明在main
線程中和Thread-4
線程中的副本值確實(shí)是不同的。
ThreadLocal存儲(chǔ)的值卸亮,在每個(gè)線程中互不影響,是不是很容易就實(shí)現(xiàn)線程安全玩裙。
我們來看下ThreadLocal的源碼兼贸,扒下它的遮羞布
先來看看ThreadLocal提供的幾個(gè)方法:
/**
* 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);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
get()
方法第一句是取得當(dāng)前線程,然后通過getMap(t)方法獲取到一個(gè)map吃溅,map的類型為ThreadLocalMap溶诞。然后接著下面獲取到<key,value>鍵值對,如果獲取成功决侈,則返回value值螺垢。如果map為空,則調(diào)用setInitialValue方法返回value赖歌。
注意:ThreadLocalMap.Entry e = map.getEntry(this);
這里傳進(jìn)去的是this
,也就是ThreadLocal枉圃。
來看看getMap()
方法中干了什么:
/**
* 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;
}
在getMap中,是調(diào)用當(dāng)期線程t庐冯,返回當(dāng)前線程t中的一個(gè)成員變量threadLocals孽亲。
那么我們繼續(xù)去Thread類中取看一下成員變量threadLocals是什么:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
實(shí)際上就是一個(gè)ThreadLocalMap,這個(gè)類型是ThreadLocal類的一個(gè)內(nèi)部類展父,我們繼續(xù)取看ThreadLocalMap的實(shí)現(xiàn):
/**
* 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 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;
}
}
可以看到ThreadLocalMap的Entry繼承了WeakReference返劲,并且使用ThreadLocal作為鍵值。
WeakReference是個(gè)什么東西呢栖茉?
它就是Java里面?zhèn)髡f的:弱引用篮绿,弱引用用來描述非必需對象的,當(dāng)JVM進(jìn)行垃圾回收時(shí)吕漂,無論內(nèi)存是否充足亲配,都會(huì)回收被弱引用關(guān)聯(lián)的對象。在java中痰娱,用java.lang.ref.WeakReference類來表示弃榨,對于弱引用我們這里就不過多的講解了,有時(shí)間我們也來扒下它神秘的外衣梨睁。
ThreadLocalMap中ThreadLocal做為key被保存在了WeakReference中鲸睛,這就說明ThreadLocal在沒有外部強(qiáng)引用時(shí),發(fā)生GC時(shí)會(huì)被回收坡贺。
然后再繼續(xù)看setInitialValue方法的具體實(shí)現(xiàn):
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
很容易了解官辈,就是如果map不為空箱舞,就設(shè)置鍵值對,為空拳亿,再創(chuàng)建Map晴股。
先來看一下 T value = initialValue()
/**
* Returns the current thread's "initial value" for this
* thread-local variable. This method will be invoked the first
* time a thread accesses the variable with the {@link #get}
* method, unless the thread previously invoked the {@link #set}
* method, in which case the {@code initialValue} method will not
* be invoked for the thread. Normally, this method is invoked at
* most once per thread, but it may be invoked again in case of
* subsequent invocations of {@link #remove} followed by {@link #get}.
*
* <p>This implementation simply returns {@code null}; if the
* programmer desires thread-local variables to have an initial
* value other than {@code null}, {@code ThreadLocal} must be
* subclassed, and this method overridden. Typically, an
* anonymous inner class will be used.
*
* @return the initial value for this thread-local
*/
protected T initialValue() {
return null;
}
這里會(huì)返回null,會(huì)報(bào)空指針,所以我么在get()的時(shí)候肺魁,必須先set()
再來电湘,看一下createMap的實(shí)現(xiàn):
/**
* 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);
}
再來看看這一行:
t.threadLocals = new ThreadLocalMap(this, firstValue);
* 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 = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
可能大部分朋友已經(jīng)明白了ThreadLocal是如何為每個(gè)線程創(chuàng)建變量的副本的:
首先,在每個(gè)線程Thread內(nèi)部有一個(gè)ThreadLocal.ThreadLocalMap類型的成員變量threadLocals鹅经,這個(gè)threadLocals就是用來存儲(chǔ)實(shí)際的變量副本的寂呛,鍵值為當(dāng)前ThreadLocal變量,value為變量副本(即T類型的變量)瘾晃。
初始化時(shí)贷痪,在Thread里面,threadLocals為空蹦误,當(dāng)通過ThreadLocal變量調(diào)用get()方法或者set()方法劫拢,就會(huì)對Thread類中的threadLocals進(jìn)行初始化,并且以當(dāng)前ThreadLocal變量為鍵值强胰,以ThreadLocal要保存的副本變量為value舱沧,存到threadLocals。
再來看看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);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
和setInitialValue()方法一樣偶洋,如果map不為空狗唉,就設(shè)置鍵值對,為空涡真,再創(chuàng)建Map分俯,createMap(t, value)我們上面已經(jīng)分析過了。
好了哆料,該到總結(jié)的時(shí)候了:
1.通過ThreadLocal創(chuàng)建的副本缸剪,存儲(chǔ)在每個(gè)線程自己的threadLocals中。
- threadLocals實(shí)際就是ThreadLocalMap东亦,ThreadLocalMap把ThreadLocal做為key杏节。
3.在進(jìn)行g(shù)et之前,必須先set典阵,否則會(huì)報(bào)空指針異常奋渔。
4.如果想在get之前不需要調(diào)用set就能正常訪問的話,必須重寫initialValue()方法壮啊。For example嫉鲸,修改一下上面的例子:
ThreadLocal<String> strLocal1 = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return Thread.currentThread().getName();
}
};
// public void setValue() {
// strLocal1.set(Thread.currentThread().getName());
// strLocal1.set("dddddd");
// }
public String getStrLocal1() {
return strLocal1.get();
}
public void Run() {
// setValue();
System.out.println("<<<<" + Thread.currentThread().getName() + ":" + getStrLocal1());
Thread thread = new Thread() {
@Override
public void run() {
super.run();
// setValue();
System.out.println("<<<<" + Thread.currentThread().getName() + ":" + getStrLocal1());
}
};
thread.start();
System.out.println("<<<<" + Thread.currentThread().getName() + ":" + getStrLocal1());
}
貼上Log:
<<<<main:main
<<<<main:main
<<<<Thread-4:Thread-4
沒有報(bào)錯(cuò)哦,趕緊動(dòng)手試試吧歹啼。