ThreadLocal作用
對於多個線程訪問一個共享變量的時候,我們往往要通過加鎖的方式進行同步,像這樣
但是除此之外,其實還有另一種方式可以隔絕線程對於共享變量讀寫的獨立性。那就是ThreadLocal。如果你創建了一個ThreadLocal變量,那么訪問這個變量的每個線程都會有一塊獨立的空間,當多個線程操作這個變量的時候,實際上操作的都是自己線程所屬的空間的那個變量,不會對其他線程有影響,也不會被其他線程影響,因為彼此都是互相獨立的。因此想要保證線程安全,也可以把共享變量放在ThreadLocal中。總體來說就是,ThreadLocal提供了線程內存儲變量的能力,這些變量不同之處在於每一個線程讀取的變量是對應的互相獨立的。通過get和set方法就可以得到當前線程對應的值。
接下來看一個例子
public class ThreadLocalDemo1 {
public static int value = 0;
static ThreadLocal<Object> local = new ThreadLocal<>();
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"---線程初始值:"+local.get());
local.set("我是"+Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName()+"---線程修改值:"+local.get());
}
},"線程"+i).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主線程"+local.get());
}
}
運行結果
線程1---線程初始值:null
線程4---線程初始值:null
線程3---線程初始值:null
線程0---線程初始值:null
線程2---線程初始值:null
線程0---線程修改值:我是線程0
線程3---線程修改值:我是線程3
線程4---線程修改值:我是線程4
線程1---線程修改值:我是線程1
線程2---線程修改值:我是線程2
主線程null
上面這段代碼,運行結果印證了我們開頭說的那些關於ThreadLocal的論述,分析一下代碼,可以看到,我們這里只有一個ThreadLocal對象,即local,我們一共有五個線程,線程0對local進行set值之后,線程2再get卻還是null,但是線程0自己再get,卻可以拿到自己設置的那個值 我是線程0
,別的線程是拿不到這個值的,而且代碼的最后,在所有的線程都運行完畢之后,在主線程對local進行get操作,拿到的值卻還是null。這就印證了這個結論,每個線程都只能拿到自己線程所屬的值,線程之間是相互獨立的
ThreadLocal原理
set方法
話不多說,我們直接上源碼。首先先看看ThreadLocal的set方法
public void set(T value) {
//先獲取當前線程
Thread t = Thread.currentThread();
//看看當前線程是不是已經設置過值了
ThreadLocalMap map = getMap(t);
//如果設置過了,就覆蓋原來的值
if (map != null)
map.set(this, value);
else
//如果當前線程第一次設置值 那就創建一個存儲區域(Map)
createMap(t, value);
}
繼續看看 createMap(t, value)方法的源碼
/**
* 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);
}
根據注釋,和源碼里面可以看到,createMap方法里面實際上就是new了一個ThreadLocalMap對象,創建了一個與ThreadLocal關聯的map,然后把當前Thread中的一個ThreadLocal引用和要設置的值放進去。請注意t.threadLocals是定義在Thread類里的,代碼如下
//這一段是定義在Thread類中的
ThreadLocal.ThreadLocalMap threadLocals = null;
繼續看看ThreadLocalMap里有啥
/**
* 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);
}
可以看到,ThreadLocalMap里維護了一個Entry數組,將Thread里面的ThreadLocal.ThreadLocalMap變量引用並且通過一系列的hashcode操作,作為key。接下來繼續看看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繼承WeakReference,使用弱引用,可以將ThreadLocal對象的生命周期和線程生命周期解綁,持有對ThreadLocal的弱引用,可以使得ThreadLocal在沒有其他強引用的時候被回收掉,這樣可以避免因為線程得不到銷毀導致ThreadLocal對象無法被回收。
以上就差不多是整個set方法的源碼了,有了以上的了解,再看get的就會很簡單
get方法
public T get() {
//獲取當前線程,並以線程引用為key去ThreadLocalMap中獲取值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//如果為不為空,就以當前線程對應的Thread中的ThreadLocal引用 進行hash計算 拿到值返回
if (map != null) {
//拿到引用去獲取值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//為空就返回一個初始值
return setInitialValue();
}
//對應getMap方法 返回的是當前線程的threadLocals引用
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//上面的getEntry方法
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
return getEntryAfterMiss(key, i, e);
}
//get中的setInitialValue方法
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;
}
可以看到,如果你一個線程在get之前沒有set,也是會給你以當前線程創建一個與你線程對應的createMap的,只不過key是當前線程下的ThreadLocalMap引用,value為null,因為
T value = initialValue();
這一行的的initialValue()方法是這樣的
protected T initialValue() {
return null;
}
其實還有remove方法啥的,原理都差不多.。所以就不贅述了。
總結
ThreadLocal為每個線程都定義了一個ThreadLocalMap類型名為threadLocals的變量,有點類似於HashMap,但是嚴格意義上來說不是,只是將其類比為Map結構,key為當前線程中的threadLocals的this引用,value是設置的值。所以每個線程都有不同的key,所能獲取到的值也是不一樣的,就是利用這種思想去保證值對每個線程的獨立性。因為不管是get還是set之前都會有currentThread這個操作,所以每個線程都只能取到自己線程對應的值。