1. 程式人生 > >詳解Java中的ThreadLocal、ThreadLocalMap和Thread之間的關係

詳解Java中的ThreadLocal、ThreadLocalMap和Thread之間的關係

每個ThreadLocal例項都有一個唯一的threadLocalHashCode(這個值將會用於在ThreadLocalMap中找到ThreadLocal對應的value值),它是通過靜態變數nextHashCode和HASH_INCREMENT進行計算的,其中nextHashCode 和HASH_INCREMENT 的定義如下

private static AtomicInteger nextHashCode = new AtomicInteger();

private static final int HASH_INCREMENT = 0x61c88647;

nextHashCode()方法如下

private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT); 
}

每個Thread中都有一個threadLocals欄位,其定義如下:

ThreadLocal.ThreadLocalMap threadLocals = null;

這個欄位在建立執行緒時為null,當呼叫ThreadLocal中的setInitialValue()和set(T value)方法時會建立ThreadLocalMap物件,這兩個方法內部都是呼叫createMap方法

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);

 }

setInitialValue()方法中設定的firstValue值是通過initialValue()方法得到的,預設的initialValue()方法返回值是null,如下所示:

protected T initialValue() {
        return null;
}

在我們自己使用時,因此通常需要override此方法。

ThreadLocalMap類中有一個靜態內部類Entry,ThreadLocal例項和對應的值都是存放在entry元素中的,類定義如下:

static class Entry extends WeakReference<ThreadLocal> {
        
            Object value;


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

Entry繼承了WeakReference類,其中WeakReference類是Reference類的子類,其構造方法就是呼叫的Reference類的構造方法:

Reference(T referent) {
this(referent, null);
}

Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

Reference中有個get()方法:

public T get() {
return this.referent;

}

其返回的即是構造方法中傳入的ThreadLocal例項

關於Reference類以及他的子類,不在本篇的討論中,將在後續部落格中進行詳解

現在分析下ThreadLocal中設定值的方法,設定值的具體操作都是呼叫

 map.set(this, value);

其內部呼叫的是ThreadLocalMap物件的

private void set(ThreadLocal key, Object value)方法

此方法大致說明下,不單獨細細展開說明了,首先通過位操作(這時本文一開始提到的threadLocalHashCode就大顯身手了)找到ThreadLocal例項在private Entry[] table中的index,然後將table[index]中的值設為新的new Entry(key, value)

取得值的方法public T get(),裡面有個獲取entry的程式碼

ThreadLocalMap.Entry e = map.getEntry(this);

具體如下:

private Entry getEntry(ThreadLocal key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);

}

首先進行位運算獲取index位置,然後檢查該位置的值是否存在,已經值是不是需要的key,e.get方法即是上文所說的返回對應的ThreadLocal例項,如果是的話就直接返回entry,如果沒有直接在hash槽中發現對應的ThreadLocal例項,進行尋找。

下面來看看具體的使用

例子1:

public class MyThreadLocal extends ThreadLocal<Long>{

Long value = new Long(1);

@Override
public Long initialValue(){
return new Long(1);
}


public static void main(String[] args) {



MyThreadLocal longThreadLocal = new MyThreadLocal();

new Thread(new LongThread(longThreadLocal, new Long(2))).start();
new Thread(new LongThread(longThreadLocal, new Long(5))).start();
new Thread(new LongThread(longThreadLocal, new Long(8))).start();
}


}

public class LongThread implements Runnable {


private ThreadLocal<Long> local;
private Long value;

public LongThread(ThreadLocal<Long> local,Long value){
this.local = local;
this.value = value;
}

@Override
public void run() {


String threadName=Thread.currentThread().getName();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName+":"+local.get());
System.out.println(threadName+": value "+value);
local.set(new Long(value+local.get()));
System.out.println(threadName+": final "+local.get());
}


}

執行結果如下:

Thread-2:1
Thread-2: value 8
Thread-2: final 9
Thread-1:1
Thread-1: value 5
Thread-1: final 6
Thread-0:1
Thread-0: value 2
Thread-0: final 3

例子2:

public class ListThreadLocal extends ThreadLocal<List<String>> {


List<String> tudouList=new ArrayList<String>();


@Override
public List<String> initialValue(){
tudouList.add("a");
tudouList.add("b");
System.out.println("List size :"+tudouList.size());
return tudouList;
}

public static void main(String[] args) {

ListThreadLocal local = new ListThreadLocal();

new Thread(new ListThread(local)).start();
new Thread(new ListThread(local)).start();
new Thread(new ListThread(local)).start();
}


}

public class ListThread implements Runnable {


private ThreadLocal<List<String>> local = new ThreadLocal<List<String>>();

public ListThread(ThreadLocal<List<String>> local){
this.local = local;
}

@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName+":"+local.get());
local.get().add(Thread.currentThread().getName());
System.out.println(threadName + ": final " + local.get());
}


}

執行結果:

List size :2
Thread-0:[a, b]
Thread-0: final [a, b, Thread-0]
List size :5
Thread-1:[a, b, Thread-0, a, b]
Thread-1: final [a, b, Thread-0, a, b, Thread-1]
List size :8
Thread-2:[a, b, Thread-0, a, b, Thread-1, a, b]

可以看到,傳遞引用型別的話,還是會改變引用的值的。
Thread-2: final [a, b, Thread-0, a, b, Thread-1, a, b, Thread-2]