天天看點

聊一聊線程變量綁定之InheritableThreadLocal示例源碼問題

通過上一節我們知道,ThreadLocal 可以用于線程變量綁定和隔離,但是卻無法做到服務調用鍊路很長時,需要做鍊路追蹤時,子線程無法擷取到父線程中的共享變量的情況,本節的 InheritableThreadLocal 就是用來解決這個問題的。

示例

@Test
public void testInheritableThreadLocal() throws InterruptedException {
    InheritableThreadLocal threadLocal = new InheritableThreadLocal();
    threadLocal.set("simple thread local in main thread!");
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("inner thread:" + threadLocal.get());
            threadLocal.set("simple ThreadLocal in thread!");
            System.out.println("inner thread:" + threadLocal.get());
        }
    });
    thread.start();
    thread.join();
    System.out.println(threadLocal.get());
}           

複制

輸出結果為:

inner thread:simple thread local in main thread!
inner thread:simple ThreadLocal in thread!
simple thread local in main thread!           

複制

可以看到,在子線程中拿到了父線程的 threadLocal 變量的值。

源碼

我們繼續按照分析 ThreadLocal 源碼的思路來分析一下 InheritableThreadLocal 變量。

Thread 構造方法

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
    .....................
     if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    ..............
}           

複制

在 Thread 中有一個 inheritableThreadLocals 變量,它的值是在構造方法中指派的,會将父 Thread 的 inheritableThreadLocals 變量傳入到目前子線程的中,并通過 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)方法建立子線程的 inheritableThreadLocals。

ThreadLocal.createInheritedMap

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

     private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }           

複制

會将父線程的 ThreadLocalMap 中的 table 中的 Entry 拷到子線程的 ThreadLocalMap 中。

在這裡我們尤其需要關注的是這一行:

Object value = key.childValue(e.value)           

複制

這在 InheritableThreadLocal 中有對應的實作:

protected T childValue(T parentValue) {
       return parentValue;
   }           

複制

直接傳回的是父線程中的 value。

其他方法

getMap

ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }           

複制

傳回的是目前線程的 inheritableThreadLocals 變量。

createMap

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

複制

建立 ThreadLocalMap 的時候也是用的 inheritableThreadLocals 引用。

InheritableThreadLocal 主要用于子線程建立時,需要自動繼承父線程的 ThreadLocal 變量,友善必要資訊的進一步傳遞。

問題

我們看下下面這兩個示例: 示例一:

@Test
    public void testMultiThreadWithoutPool(){
        InheritableThreadLocal threadLocal = new InheritableThreadLocal();
        IntStream.range(0,10).forEach(i -> {
            threadLocal.set(i);
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            }).start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }           

複制

輸出結果:

Thread-0:0
Thread-1:1
Thread-2:2
Thread-3:3
Thread-4:4
Thread-5:5
Thread-6:6
Thread-7:7
Thread-8:8
Thread-9:9           

複制

示例二:

private ExecutorService service = Executors.newFixedThreadPool(1);

    @Test
    public void testInheritableByThreadPool(){
        InheritableThreadLocal threadLocal = new InheritableThreadLocal();
        IntStream.range(0,10).forEach(i -> {
            System.out.println(i);
            threadLocal.set(i);
            service.submit(() -> {
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            });
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }           

複制

輸出結果:

0
pool-1-thread-1:0
1
pool-1-thread-1:0
2
pool-1-thread-1:0
3
pool-1-thread-1:0
4
pool-1-thread-1:0
5
pool-1-thread-1:0
6
pool-1-thread-1:0
7
pool-1-thread-1:0
8
pool-1-thread-1:0
9
pool-1-thread-1:0           

複制

對比兩者的結果可以發現,同樣的 set 操作,結果大不相同。這是因為示例一是每次 new Thread 的操作都會将父線程的 ThreadLocal 變量傳入子線程中,示例二是線程池的操作,線程隻會初始化一次,子線程是取不到父線程變量的實時變動的。關于這個問題,下篇文章中将要講的 TransmittableThreadLocal 可以完美解決。