簡單學:併發程式設計之 ThreadLocal

LemonYang發表於2019-03-04

關於java併發程式設計的相關文章都是閱讀了《java併發程式設計實戰》之後的讀書筆記總結

概述

ThreadLocal其實是執行緒封閉的一種規範化的實現,它通過提供一組get和set的介面為每個使用該變數的執行緒儲存一份獨立的副本。對於那種按執行緒多例項(每個執行緒對應一個例項)的物件的訪問,並且這個物件很多地方都要用到的情況(例如資料庫連線管理、會話session管理以及執行緒私有的訊息佇列等),ThreadLocal就會展現出它的魅力

下面的這個小例子展示了ThreadLocal的常規使用:

public class ThreadResource {

    private String threadName;
    private int threadId;

    public ThreadResource(int threadId, String threadName) {
        this.threadId = threadId;
        this.threadName = threadName;
    }

    public String getThreadName() {
        return threadName;
    }

    public void setThreadName(String threadName) {
        this.threadName = threadName;
    }

    public int getThreadId() {
        return threadId;
    }

    public void setThreadId(int threadId) {
        this.threadId = threadId;
    }
}

public class Test {
    //以一個靜態例項的方式持有一個ThreadLocal物件,它裡面以map的形式儲存了執行緒的區域性變數
    static ThreadLocal<ThreadResource> resoursePackage = new ThreadLocal<ThreadResource>() {
        @Override
        protected ThreadResource initialValue() {
            return new ThreadResource(0, "initialThread");
        }
    };

    private static class TestThread extends Thread {
        @Override
        public void run() {
            resoursePackage.set(new ThreadResource(1, "testThread"));
            System.out.println(resoursePackage.get().getThreadName() + resoursePackage.get().getThreadId());
        }
    }

    public static void main(String[] args) {
        System.out.println(resoursePackage.get().getThreadName() + resoursePackage.get().getThreadId());
        new TestThread().start();
    }
}複製程式碼

原始碼解析ThreadLocal的實現

  • get()方法的實現
public T get() {
    //獲取當前threadlocal變數所屬的執行緒
    Thread t = Thread.currentThread();
    //根據執行緒獲取到一個ThreadLocalMap的物件
    ThreadLocalMap map = getMap(t);
    //如果執行緒已經繫結了一個ThreadLocalMap物件的話,則從中獲取到裡面所儲存的值,否則使用初始化的值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T) e.value;
            return result;
        }
    }
    return setInitialValue();
}複製程式碼

我們看一下上面getMap()的方法的實現

ThreadLocalMap getMap(Thread t) {
    //返回執行緒的一個ThreadLocalMap的成員變數,下面是該成員變數在threa類中的宣告
    //ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class.
    //ThreadLocal.ThreadLocalMap threadLocals = ull;
    return t.threadLocals;
}複製程式碼

再看一下執行緒尚未繫結ThreadLocalMap物件的時候,呼叫的 setInitialValue() 的方法的實現

private T setInitialValue() {
    //initialValue()就是我們在新建一個ThreadLocal變數的時候,實現的protected的那個方法。
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        //由此我們可知,執行緒尚未繫結到ThreadLocalMap物件的時候
        //ThreadLocal為我們使用在initialValue設定的值初始化了一個物件值,並繫結到該執行緒上。
        createMap(t, value);
    return value;
}複製程式碼

在上面的程式碼中,我們一直提及到了一個ThreadLocalMap的類,它其實是在ThreadLocal的一個靜態內部類,它是一個ThreadLocal自定義的hash map物件,用於儲存執行緒的區域性變數。在它裡面,又包含了一個Entry的靜態內部類,它裡面就是對應的所要儲存的值。我們看一下原始碼的實現

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        //以ThreadLocal物件作為鍵值,儲存threadlocal變數所包含的值
        //我們在ThreadLocal的get方法當中也是根據threadlocal變數取出所儲存的值
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
}複製程式碼
  • set()方法的實現
public void set(T value) {
    //獲取threadlocal物件所屬的執行緒
    Thread t = Thread.currentThread();
    //獲取執行緒所繫結的ThreadLocalMap物件
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        //線上程尚未初始化並繫結ThreadLocalMap物件的時候,使用給定的value值新建一個,並將執行緒與該對物件關聯起來
        createMap(t, value);
}複製程式碼

在閱讀了上面的原始碼之後,我們大概已經明白了ThreadLocal是怎麼做到為每個執行緒儲存一份引用物件的拷貝的值的。每個thread物件都會持有一個ThreadLocal.ThreadLocalMap的物件的引用,而我們通過ThreadLocal物件找到了執行緒所持有的這個ThreadLocalMap物件,並往其中新增、移除或獲得我們所要儲存的引用物件的值。

關於ThreadLocalMap裡面實現的自定義的hash map我們可以在ThreadLocal的原始碼中深入瞭解,這裡不做進一步的深入。

相關文章