【並發編程】InheritableThreadLocal使用詳解



本博客系列是學習並發編程過程中的記錄總結。由於文章比較多,寫的時間也比較散,所以我整理了個目錄貼(傳送門),方便查閱。

並發編程系列博客傳送門


引子

public class InheritableThreadLocalDemo {

    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        threadLocal.set("mainThread");
        System.out.println("value:"+threadLocal.get());
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                String value = threadLocal.get();
                System.out.println("value:"+value);
            }
        });
        thread.start();
    }

}

上面代碼中在主線程中設置了一個ThreadLocal變量,並將其值設置為mainThread。然后有在主線程中開啟了一個子線程thread,並試圖獲取在主線程中set的ThreadLocal變量的值。但是結果如下:

value:mainThread
value:null

通過前面的文章介紹,對於上面的結果我們也就非常容易理解了。每個線程都會有一個自己的ThreadLocalMap,所以子線程在調用get方法拿值的時候其實訪問的是自己的ThreadLocalMap,這個Map和主線程的Map是兩個不同的對象,所以肯定是拿不到值的。

那么Java中有沒有類似的對象能實現上面的功能呢?有,InheritableThreadLocal就能實現這樣的功能,這個類能讓子線程繼承父線程中已經設置的ThreadLocal值。

InheritableThreadLocal簡單使用

還是以上面的列子為列,我們只需要將ThreadLocal變成InheritableThreadLocal就行了。

public class InheritableThreadLocalDemo {

    private static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        threadLocal.set("mainThread");
        System.out.println("value:"+threadLocal.get());
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                String value = threadLocal.get();
                System.out.println("value:"+value);
            }
        });
        thread.start();
    }

}

執行結果如下:

value:mainThread
value:mainThread

InheritableThreadLocal原理分析

先看下InheritableThreadLocal的源代碼:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
   
    protected T childValue(T parentValue) {
        return parentValue;
    }

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

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

這個類繼承了ThreadLocal,並且重寫了getMap和createMap方法,區別就是將 ThreadLocal 中的 threadLocals 換成了 inheritableThreadLocals,這兩個變量都是ThreadLocalMap類型,並且都是Thread類的屬性。

下面就一步步來看下InheritableThreadLocal為什么能拿到父線程中的ThreadLocal值。

step1:InheritableThreadLocal獲取值先調用了get方法,所以我們直接看看get方法都做了些啥。

  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方法和ThreadLocal中是一樣的,唯一有區別的就是其中的getMap方法重寫了,返回的是inheritableThreadLocals屬性。這個屬性也是一個ThreadLocalMap類型的變量。那么從這邊就可以推斷出來:肯定是在某處將父線程中的ThreadLocal值賦值到了子線程的inheritableThreadLocals中。

step2:在源代碼中搜索哪些地方使用到了inheritableThreadLocals這個屬性,最后找到這段代碼:

private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name.toCharArray();
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        g.checkAccess();
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        g.addUnstarted();
        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        //1. 這邊先判斷了父線程中inheritableThreadLocals屬性是否為空,不為空的話就復制給子線程
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

上面的代碼印證了我們的猜想。需要注意的是一旦子線程被創建以后,再操作父線程中的ThreadLocal變量,那么子線程是不能感知的。因為父線程和子線程還是擁有各自的ThreadLocalMap,只是在創建子線程的“一剎那”將父線程的ThreadLocalMap復制給子線程,后續兩者就沒啥關系了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM