一、前言
我們在前面學習知道對象和變量的併發訪問我們可以使用synchronized、volatile關鍵字。synchronized保證了數據的原子性,解決併發訪問同一數據的時候排隊執行。volatile保證了數據的可見性,使得在多線程共享一個數據的時候,一旦數據發生改變。各個線程都能拿到最新的數據的之。
而ThreadLocal的出現使得每個線程都有自己的一份副本,也就是使得每個線程都單獨擁有自己的數據。達到線程之間的數據隔離。
二、ThreadLocal的使用
package com.it.test.thread;
public class ThreadLocalDemo {
static ThreadLocal<Integer> local1 = new ThreadLocal<>() {
@Override
protected Integer initialValue() {
return 1;
}
};
public static void main(String[] args) {
new Thread1().start();
new Thread2().start();
}
static class Thread1 extends Thread {
@Override
public void run() {
local1.set(1);
System.out.println(Thread.currentThread().getName()+":"+local1.get());
}
}
static class Thread2 extends Thread {
@Override
public void run() {
local1.set(2);
System.out.println(Thread.currentThread().getName()+":"+local1.get());
}
}
}
Thread-0:1
Thread-1:2
三、ThreadLocal源碼分析
1、set方法
獲取當前線程,拿到當前線程的ThreadLocalMap ,如果map爲空,則創建一個map,否則之間給當前map設置值,key爲當前ThreacLocal對象,值就是我們存放的值。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
來看下createMap方法,爲當前線程設置了一個ThreadLocalMap對象,
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
來看下ThreadLocalMap的源碼,包含了一個靜態內部類Entry,繼承了弱引用的ThreadLocal。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
在上面爲線程創建一個ThreadLocalMap對象的時候,調用了ThreadLocalMap的構造方法,在ThreadLocalMap的構造方法,構造方法傳入了:
ThreadLocal<?> firstKey, Object firstValue兩個參數。
接着內部創建了內部類的Entry類型數組,
table = new Entry[INITIAL_CAPACITY];
然後創建Entry對象,傳入firstKey和firstValue,存入到Entry數組中。
這裏需要注意的是,之所以創建了一個Entry數組,是因爲一個線程可能有多個ThreadLocal對象,
每一個ThreadLocal,都會在ThreadLocalMap內部的Entry數組存入。
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);
}
也就是當我們在某個線程裏通過ThreadLocal設置值的時候,那麼就會在當前線程中創建一個ThreadLocalMap,如果存在ThreadLocalMap則直接獲取當前線程的ThreadLocalMap。而ThreadLocalMap包含一個Entry數組,Entry有兩個屬性key和value。使用ThreadLocal設置值的時候,就會把ThreadLocal對象作爲key,以及具體設置的值,初始化給Entry對象,然後存入Entry類型的數組中。當有多個
ThreadLocal的時候,數組就會存入多個Entry對象。
2、get方法
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();
}
首先拿到當前線程,然後獲取當前線程的ThreadLocalMap 。調用ThreadLocalMap 的getEntry方法。
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.refersTo(key))
return e;
else
return getEntryAfterMiss(key, i, e);
}
我們知道ThreadLocalMap 中的包含了一個Entry數組,getEntry方法,傳入了ThreadLocal對象作爲key,然後經過系列的位運算,算出數組的下標。最好從數組中拿到Entry對象,
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
拿到Entry對象,直接獲取Entry對象裏的value值,就是我們存入的value值。
小結
- ThreadLocal使得每個線程擁有自己的一份副本,也就是擁有自己的一份數據。達到線程直接數據隔離
- ThreadLocal的實現原理的核心就是:
(1)當我們在某個線程下第一次使用ThreadLocal設置值的時候,那麼就會爲這個線程創建一個ThreadLocalMap 對象,ThreadLocalMap 中包含了一個Entry數組。Entry中包含了兩個屬性key和value。
在設置值的時候,就會將我們的值存放到ThreadLocalMap 的Entry數組中,其中key爲當前ThreadLocal對象,值就是我們設置的值。
(2)當我們使用ThreadLocal獲取值的時候,首先是會獲取當前線程的ThreadLocalMap ,然後獲取ThreadLocalMap 中的數組。接着通過當前ThreadLocal作爲key,通過位運算獲取到屬性key爲當前ThreadLocal對象的Entry對象,在獲取Entry中的value屬性。就是我們需要獲取的值。