天天看點

Java多線程--InheritableThreadLocal--使用/執行個體

簡介

        本文介紹InheritableThreadLocal的用法。

        ThreadLocal可以将資料綁定目前線程,如果希望目前線程的ThreadLocal的資料被子線程使用,實作方式就會相當困難(需要使用者自己在代碼中傳遞)。

        InheritableThreadLocal可以友善地讓子線程自動擷取父線程ThreadLocal的資料。

        ThreadLocal和InheritableThreadLocal都要注意,用完後要調用其remove()方法,不然可能導緻記憶體洩露或者産生髒資料。

問題複現

代碼

package com.example.a;

public class Demo {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        threadLocal.set("hello");
        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);
                // 一定要remove,不然可能導緻記憶體洩漏
                threadLocal.remove();
            }
        });
        thread.start();
    }

}      

結果(子線程無法擷取父線程設定的值)

主線程擷取的value:hello
子線程擷取的value:null      

解決方案

隻需要将ThreadLocal變成InheritableThreadLocal。

代碼

package com.example.a;

public class Demo {
    private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        inheritableThreadLocal.set("hello");
        System.out.println("主線程擷取的value:" + inheritableThreadLocal.get());

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                String value = inheritableThreadLocal.get();
                System.out.println("子線程擷取的value:" + value);
                // 一定要remove,不然可能導緻記憶體洩漏
                inheritableThreadLocal.remove();
            }
        });
        thread.start();
    }

}      

結果(子線程可以擷取父線程設定的值)

主線程擷取的value:hello
子線程擷取的value:hello      

源碼分析

源碼檢視

InheritableThreadLocal的源代碼:

package java.lang;
import java.lang.ref.*;

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方法,差別是:InheritableThreadLocal将 ThreadLocal 中的 threadLocals 換成了 inheritableThreadLocals,這兩個變量都是ThreadLocalMap類型,并且都是Thread類的屬性。

InheritableThreadLocal為什麼能拿到父線程中的ThreadLocal值?

1.InheritableThreadLocal的get方法

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中。 

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

注意