首先ThreadLoacl是什麼?之前看有些博客發現會有人這麼介紹ThreadLoacl:
它可以解決線程併發問題
它可以解決線程共享數據問題。。。
。。。。
百事不得姐的我決定翻閱源碼進行一探究竟!
首先看看怎麼用
public class MainActivity extends AppCompatActivity {
ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "null";
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for (int i = 0; i < 10; i++) {
new myThread().start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class myThread extends Thread {
@Override
public void run() {
Log.e(Thread.currentThread().getName(),threadLocal.get());
threadLocal.set(Thread.currentThread().getName());
Log.e(Thread.currentThread().getName(),threadLocal.get());
}
}
}
首先我們創建一個ThreadLocal對象,然後創建十個線程,每個線程將線程名稱存儲在threadloacl中,然後我們在存儲前後打印出來對應的get值,結果如下:
2019-01-02 14:04:25.734 16502-16530/com.lxf.myapplication E/Thread-4: null
2019-01-02 14:04:25.734 16502-16530/com.lxf.myapplication E/Thread-4: Thread-4
2019-01-02 14:04:25.937 16502-16531/com.lxf.myapplication E/Thread-5: null
2019-01-02 14:04:25.937 16502-16531/com.lxf.myapplication E/Thread-5: Thread-5
2019-01-02 14:04:26.137 16502-16532/com.lxf.myapplication E/Thread-6: null
2019-01-02 14:04:26.137 16502-16532/com.lxf.myapplication E/Thread-6: Thread-6
2019-01-02 14:04:26.340 16502-16533/com.lxf.myapplication E/Thread-7: null
2019-01-02 14:04:26.340 16502-16533/com.lxf.myapplication E/Thread-7: Thread-7
2019-01-02 14:04:26.544 16502-16534/com.lxf.myapplication E/Thread-8: null
2019-01-02 14:04:26.544 16502-16534/com.lxf.myapplication E/Thread-8: Thread-8
2019-01-02 14:04:26.744 16502-16535/com.lxf.myapplication E/Thread-9: null
2019-01-02 14:04:26.744 16502-16535/com.lxf.myapplication E/Thread-9: Thread-9
2019-01-02 14:04:26.947 16502-16536/com.lxf.myapplication E/Thread-10: null
2019-01-02 14:04:26.947 16502-16536/com.lxf.myapplication E/Thread-10: Thread-10
2019-01-02 14:04:27.149 16502-16537/com.lxf.myapplication E/Thread-11: null
2019-01-02 14:04:27.149 16502-16537/com.lxf.myapplication E/Thread-11: Thread-11
2019-01-02 14:04:27.350 16502-16538/com.lxf.myapplication E/Thread-12: null
2019-01-02 14:04:27.350 16502-16538/com.lxf.myapplication E/Thread-12: Thread-12
2019-01-02 14:04:27.552 16502-16539/com.lxf.myapplication E/Thread-13: null
2019-01-02 14:04:27.553 16502-16539/com.lxf.myapplication E/Thread-13: Thread-13
結果在我們的預想中
然後我們看一下源碼到底是怎麼回事
首先我們看一下set方法做了什麼事情
public void set(T value) {
//首先去拿一下當前調用的線程
Thread t = Thread.currentThread();
//這裏拿到一個ThreadLocalMap
ThreadLocalMap map = getMap(t);
//ThreadLocalMap 不爲null的時候將其設置進去
if (map != null)
map.set(this, value);
//否則創建一個map
else
createMap(t, value);
}
這裏我們可以看到set的一個流程就是將參數傳入的value設置到了一個ThreadLocalMap 中。那麼這個ThreadLocalMap 是什麼呢?
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
我們發現這裏直接返回了當前調用線程的一個變量threadLocals,
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Thread中我們發現初始值是null
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
然後我們發現在creatMap中初始化了這個變量,默認的第一個鍵值對就是當前初始化這個變量的 ThreadLocal實例和要外界線程中存入ThreadLocal的值。
ThreadLocalMap是ThreadLocal中的一個內部類,
這裏我們發現map中的鍵值對是存放在一個table數組中的,並且對Entry做了弱引用,這樣的話當我們的線程釋放之後,內部的ThreadLoacl中的數據也會被回收。以防出現內存泄露的情況!
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
.....................................................................................................................................
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);
}
這裏的map和hashmap 的實現有所不同,ThreadLocal內部維護着一個threadLocalHashCode ,是根據這個threadLocal的threadLocalHashCode 來計算出一個數組的存放索引!這是一個自定義計算出來的hashcode,消除避免了哈希碰撞的情況!所以計算出來的索引都是唯一的!
然後我們看看這個map的set方法
private void set(ThreadLocal<?> key, Object value) {
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();
}
上面的方法我們可以看到,其實就是做了一系列的判斷,判空,替換,數組擴容等操作,本質就是將要存放的value以threadlocal爲key存放起來。
然後我們看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();
}
很明瞭,threadlocal會根據當前調用的線程來獲取當前線程中的threadlocalmap,然後從中取出key爲該threadlocal的value,這個value就是之前存入的值。
那麼通過源碼的分析,我們發現threadlocal並不能實現上面的併發問題,因爲實際上根本就沒有併發相關的問題。存入threadlocal中的數據實際上是存入了對應的調用線程中的內部的一個map中,與threadlocal相關的實際上只是將其作爲一個key引用起來,作爲取值的憑證而已。同理我們發現threadlocal的做法也並沒有實現數據線程間共享的操作,相反是做到了數據的線程間隔離,各個線程只能訪問各個線程的數據,做到了線程的數據安全。
實際上我們發現 ThreadLocal可以做到:
1,數據的線程內全局使用,維持了代碼的簡潔,避免了大量的函數傳值
2,做到了線程與線程的數據隔離,使得線程的數據變得安全,不用考慮多個線程操作同一數據造成的併發安全問題。
3,內部使用弱引用解決了可能出現的內存泄露問題。