1. 程式人生 > >[java] ThreadLocal類解析

[java] ThreadLocal類解析

概述

該類的作用是提供執行緒區域性(thread-local)的變數, 這些變數與正常變數的不同之處在於(通過其get或set方法)訪問一個執行緒的每個執行緒都有自己的獨立初始化的變數副本。ThreadLocal例項通常是希望儲存狀態與執行緒(例如,使用者ID或事務ID)關聯的私有靜態欄位。

例如,下面的類生成每個執行緒本地的唯一識別符號。第一次呼叫ThreadId.get方法時,分配一個執行緒的ID,在後續呼叫中保持不變。

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadId {
     // Atomic integer containing the next thread ID to be assigned
     private static final AtomicInteger nextId = new AtomicInteger(0);

     // Thread local variable containing each thread's ID
     private static final ThreadLocal<Integer> threadId =
         new ThreadLocal<Integer>() {
             @Override 
             protected Integer initialValue() {
                 return nextId.getAndIncrement();
         }
     };

     // Returns the current thread's unique ID, assigning it if necessary
     public static int get() {
         return threadId.get();
     }
 }

每個執行緒都保持對執行緒本地變數的副本的隱式引用,只要執行緒是活的並且ThreadLocal例項是可訪問的;線上程離開後,執行緒本地例項的所有副本都由垃圾收集的處理(除非對這些副本有其他引用)。

Set方法

程式碼實現如下:

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

首先根據呼叫這個方法的物件獲取當前執行緒,然後獲取屬於這個執行緒的ThreadLocalMap例項。 ThreadLocalMap是ThreadLocal的一個內部類,這個類的程式碼佔據了ThreadLocal類的一半還多。可見其重要性。Threadlocal的方法都是基於這個類實現的。

從上面程式碼我們可以看到,如果map不為空就呼叫set方法

/**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            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();
        }

Key即為傳入的引數this,即呼叫這個set方法的ThreadLocal例項,我們知道每個執行緒都會有自己對應的ThreadLocal例項,而這裡把這個例項拿來作為了map的key,因此,這就可以解釋清楚了為什麼ThreadLocal是執行緒之間隔離的。

實現的邏輯比較簡單:

根據傳入的key,藉助了Unsafe類去計算它的hashcode值,然後value不用做任何其他處理。最後是儲存在了一個Entry型別的數組裡面。

Entry則是ThreadLocalMap這個類的有一個內部類,定義如下:

 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

可以看到,這個類裡面有一個Object型別的成員變數value,這個成員變數就是我們拿來儲存值的變數。

get方法

實現程式碼如下:

 /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

有一個很有意思的問題,這個方法不是get方法嗎?那它為什麼沒有引數,沒有key呢?其實在分析set方法的時候我們已經回答了這個問題了,ThreadLocal的map的key是ThreadLocal例項本身。再看程式碼:首先仍舊是獲取了當前執行緒,再去拿了它的ThreadLocalMap,這個時候,直接去呼叫了這個map的getEntry方法,然後傳入的引數是this ,即這個ThreadLocal物件本身。非常巧妙的邏輯。

remove方法

  public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

呼叫了ThreadLocalMap的remove方法,引數是ThreadLocal例項,也就是map的key,即這裡的this。實現如下

     /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

遍歷table,找到這個key對於的Entry,然後把它的值設為null。

ThreadLocal Map定義

在ThreadLocal類中,物件是儲存在一個內部類ThreadLocalMap的成員變數table裡面的,定義如下:

static class ThreadLocalMap {

        /**
         * 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;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
        ...
}

可以看到table是一個Entry型別的陣列,Entry也是一個類,這個類裡面又有一個成員變數Object型別的value,這就是ThreadLocal這個類儲存值得地方。

典型使用場景

比較出名的一個應用場景是 Web伺服器端的Session的儲存。

Web容器採用執行緒隔離的多執行緒模型,也就是每一個請求都會對應一條執行緒,執行緒之間相互隔離,沒有共享資料。這樣能夠簡化程式設計模型,程式設計師可以用單執行緒的思維開發這種多執行緒應用。

當收到一個請求時,可以將當前Session資訊儲存在ThreadLocal中,在請求處理過程中可以隨時使用Session資訊,每個請求之間的Session資訊互不影響。當請求處理完成後通過remove方法將當前Session資訊清除即可。