Java併發程式設計 -- ThreadLocal

微笑面對生活發表於2018-08-26

ThreadLocal一般稱為執行緒本地變數,它是一種特殊的執行緒繫結機制,將變數與執行緒繫結在一起,為每一個執行緒維護一個獨立的變數副本。通過ThreadLocal可以將物件的可見範圍限制在同一個執行緒內。

Demo

public class Demo {
    private ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);

    public int getNext(){
        Integer integer = threadLocal.get();
        integer++;
        threadLocal.set(integer);
        return integer;
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        new Thread(() -> {
            while (true){
                System.out.println(Thread.currentThread().getName()+"   :"+demo.getNext());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(() -> {
            while (true){
                System.out.println(Thread.currentThread().getName()+"   :"+demo.getNext());
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
複製程式碼
Thread-0   :1
Thread-1   :1
Thread-1   :2
Thread-1   :3
Thread-1   :4
Thread-1   :5
Thread-1   :6
Thread-1   :7
Thread-1   :8
Thread-1   :9
Thread-1   :10
Thread-0   :2
......
複製程式碼

我們可以看到,執行緒0和執行緒1是相互不干擾的

原始碼分析

demo中,我們用到ThreadLocalget()方法和set()方法,我們看看它底層是怎麼實現的,當然,還有個remove()方法。這三個是最核心的API。

1. get()

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        //如果map不為空,則拿出他的Entry,進而拿出entry的value
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
複製程式碼

return setInitialValue();中的setInitialValue():

1.1 setInitialValue()
    private T setInitialValue() {
        //呼叫下面的initialValue方法
        T value = initialValue();
        //獲取當前執行緒
        Thread t = Thread.currentThread();
        //通過當前執行緒來獲取map
        ThreadLocalMap map = getMap(t);
        //如果map不為空,則set,如果為空,new一個ThreadLocalMap(createMap方法)
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    protected T initialValue() {
        return null;
    }
複製程式碼
1.2 createMap
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
複製程式碼
1.3 getMap
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    ThreadLocal.ThreadLocalMap threadLocals = null;
複製程式碼
1.4 Entry
       static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
複製程式碼
1.5 ThreadLocal和ThreadLocalMap對應關係

ThreadLocal和ThreadLocalMap對應關係

2. set(T value)

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
複製程式碼

set()方法和上文的setInitialValue()方法很相似。不過一個是面向業務程式設計,一個是物件初始化(無參)。

2.1 remove
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     } 
複製程式碼

m.remove(this) 的 remove() 方法

2.2 ThreadLocalMap.remove
/**
 * Remove the entry for key.
 */
private void remove(ThreadLocal<?> key) {
    //獲取所有的Entry
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    //遍歷查詢 key,如果存在,clear對應的Entry
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}
複製程式碼

3. 總結

我們可以看到,ThreadLocalThread物件進行繫結,然後將Threadkey(實際上key並不是ThreadLocal本身,而是它的一個弱引用),Objectvalue存放在map中。也就是說每個執行緒有一個自己的ThreadLocalMap。呼叫get方法時,獲取到當前物件然後獲取value進而進行操作,在往某個ThreadLocal裡塞值的時候,都會往自己的ThreadLocalMap裡存,讀也是以某個ThreadLocal作為引用,在自己的map裡找對應的key,從而實現了執行緒隔離。

相關文章