1. 程式人生 > 其它 >java併發:lnheriitableThreadLocal

java併發:lnheriitableThreadLocal

初識lnheriitableThreadLocal

lnheriitableThreadLocal繼承自 ThreadLocal,其提供了一個特性,讓子執行緒可以訪問在父執行緒中設定的本地變數。

程式碼示例

  public static void main(String[] args) {

    ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>(){
      @Override
      protected Integer initialValue() {
        return
0; } }; threadLocal.set(1); Thread newThread = new Thread() { @Override public void run() { System.out.println(threadLocal.get()); } }; newThread.start(); }

上面這段程式碼,使得newThread訪問到了main方法所線上程中的threadLocal變數的變動,即此處newThread將輸出1。

請對比下面這段程式碼:

  public
static void main(String[] args) { ThreadLocal<Integer> threadLocal = new ThreadLocal<>(){ @Override protected Integer initialValue() { return 0; } }; threadLocal.set(1); Thread newThread = new Thread() { @Override public void run() { System.out.println(threadLocal.get()); } }; newThread.start(); }

此段程式碼與第一個例子不同的地方在於threadLocal變數的型別不一樣,此處newThread將輸出0。

原始碼分析

此處將跟蹤Thread的建立過程,以搞明白為什麼lnheriitableThreadLocal型別的變數可以暴露在新建立的執行緒中。

    /**
     * Allocates a new {@code Thread} object. This constructor has the same
     * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
     * {@code (null, target, gname)}, where {@code gname} is a newly generated
     * name. Automatically generated names are of the form
     * {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
     *
     * @param  target
     *         the object whose {@code run} method is invoked when this thread
     *         is started. If {@code null}, this classes {@code run} method does
     *         nothing.
     */
    public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
    }
    public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
        this(group, target, name, stackSize, null, true);
    }
    /**
     * Initializes a Thread.
     *
     * @param g the Thread group
     * @param target the object whose run() method gets called
     * @param name the name of the new Thread
     * @param stackSize the desired stack size for the new thread, or
     *        zero to indicate that this parameter is to be ignored.
     * @param acc the AccessControlContext to inherit, or
     *            AccessController.getContext() if null
     * @param inheritThreadLocals if {@code true}, inherit initial values for
     *            inheritable thread-locals from the constructing thread
     */
    private Thread(ThreadGroup g, Runnable target, String name,
                   long stackSize, AccessControlContext acc,
                   boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security manager doesn't have a strong opinion
               on the matter, use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(
                        SecurityConstants.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);
        if (inheritThreadLocals && 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 */
        this.tid = nextThreadID();
    }

上述程式碼中著色部分是關鍵,至此將跳轉到ThreadLocal中,程式碼如下:

    /**
     * Factory method to create map of inherited thread locals.
     * Designed to be called only from Thread constructor.
     *
     * @param  parentMap the map associated with parent thread
     * @return a map containing the parent's inheritable bindings
     */
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

上述程式碼呼叫了ThreadLocalMap的建構函式,程式碼如下:

        /**
         * Construct a new map including all Inheritable ThreadLocals
         * from given parent map. Called only by createInheritedMap.
         *
         * @param parentMap the map associated with parent thread.
         */
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (Entry e : parentTable) {
                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++;
                    }
                }
            }
        }

上述程式碼中著色部分呼叫了ThreadLocal中的childValue方法,該方法的程式碼如下:

    /**
     * Method childValue is visibly defined in subclass
     * InheritableThreadLocal, but is internally defined here for the
     * sake of providing createInheritedMap factory method without
     * needing to subclass the map class in InheritableThreadLocal.
     * This technique is preferable to the alternative of embedding
     * instanceof tests in methods.
     */
    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

上述方法由lnheriitableThreadLocal重寫,與此同時,lnheriitableThreadLocal重寫了getMap和createMap方法,整體程式碼如下:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * Computes the child's initial value for this inheritable thread-local
     * variable as a function of the parent's value at the time the child
     * thread is created.  This method is called from within the parent
     * thread before the child is started.
     * <p>
     * This method merely returns its input argument, and should be overridden
     * if a different behavior is desired.
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

總結:

執行緒在通過 InheritableThreadLocal類例項的 set或者 get方法設定變數時,會建立當前執行緒的 inheritableThreadLocals變數。

InheritableThreadLocal類通過重寫程式碼,讓本地變數儲存到了具體執行緒的 inheritableThreadLocals變數裡面。

當父執行緒建立子執行緒時,建構函式會把父執行緒中 inheritableThreadLocals變數裡面的本地變數複製一份儲存到子執行緒的 inheritableThreadLocals 變數裡面。

思考:

在什麼情況下子執行緒需要獲取父執行緒的 threadlocal 變數呢,實現方式有哪些?

最簡單的實現方式:建立執行緒時傳入父執行緒中的變數,將其複製到子執行緒中