ThreadLocal,InheritableThreadLocal,TransmittableThreadLocal分析

前言

ThreadLocal在多个线程中针对一个线程维护不同值的功能,这样我们就不需要在每个线程内都传这个参数。ThreadLocal的子类InheritableThreadLocal在ThreadLocal的基础上,解决了父子线程值传递的问题。但是在线程池中由于线程的复用,线程在创建的时候会把父线程当前的inheritableThreadLocals拷贝过去,之后我们再在代码中设置了InheritableThreadLocal后,在线程池中的线程就再也获取不到这个新的InheritableThreadLocal,导致数据错乱,阿里开源的TransmittableThreadLocal框架就是解决这个问题。

ThreadLocal分析

首先看个ThreadLocal的小例子

package com.spring.learn.threadlocal;

/**
 * @author : caoyu create at: 2019-11-07
 * @description:
 */
public class ThreadLocalTest {

	public static void main(String s[]) {
		ThreadLocal threadLocal1 = new ThreadLocal();
		threadLocal1.set("threadLocal1");
		new Thread(new Runnable() {
			@Override
			public void run() {
				ThreadLocal threadLocal2 = new ThreadLocal();
				System.out.println(Thread.currentThread().getName() + ":" + threadLocal1.get());
				threadLocal2.set("threadLocal2");
				System.out.println(Thread.currentThread().getName() + ":" + threadLocal2.get());
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				ThreadLocal threadLocal3 = new ThreadLocal();
				System.out.println(Thread.currentThread().getName() + ":" + threadLocal1.get());
				threadLocal3.set("threadLocal3");
				System.out.println(Thread.currentThread().getName() + ":" + threadLocal3.get());
			}
		}).start();
		System.out.println(Thread.currentThread().getName() + ":" + threadLocal1.get());
	}

}

运行结果为:

Thread-0:null
Thread-0:threadLocal2
Thread-1:null
Thread-1:threadLocal3
main:threadLocal1

我们看到不同线程间threadLocal的值是不一样的,那是如何做到的呢?
ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值,ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。

 	public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
	ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
	void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

ThreadLocal的set方法会根据当前线程返回Thread的变量ThreadLocal.ThreadLocalMap threadLocals,如果为空会新建一个ThreadLocalMap,并将值赋值给当前线程的threadLocals变量。

    private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            //获取索引值,这个地方是比较特别的地方
            int i = key.threadLocalHashCode & (len-1);
            //遍历tab如果已经存在则更新值
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
public class ThreadLocal<T> {
    /**
     * ThreadLocals rely on per-thread linear-probe hash maps attached
     * to each thread (Thread.threadLocals and
     * inheritableThreadLocals).  The ThreadLocal objects act as keys,
     * searched via threadLocalHashCode.  This is a custom hash code
     * (useful only within ThreadLocalMaps) that eliminates collisions
     * in the common case where consecutively constructed ThreadLocals
     * are used by the same threads, while remaining well-behaved in
     * less common cases.
     */
    private final int threadLocalHashCode = nextHashCode();

    /**
     * The next hash code to be given out. Updated atomically. Starts at
     * zero.
     */
    private static AtomicInteger nextHashCode =
        new AtomicInteger();

    /**
     * The difference between successively generated hash codes - turns
     * implicit sequential thread-local IDs into near-optimally spread
     * multiplicative hash values for power-of-two-sized tables.
     */
    private static final int HASH_INCREMENT = 0x61c88647;

    /**
     * Returns the next hash code.
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

因为static的原因,在每次new ThreadLocal时因为threadLocalHashCode的初始化,会使threadLocalHashCode值自增一次,增量为0x61c88647。
由此可以看出每个ThreadLocal的索引值都是不同的,根据ThreadLocal的索引下标值构建Entry并存放至Entry[]数组中。

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

理解ThreadLocal的set方法再去看get方法就比较容易了,从上面的源码中可以看到根据当前线程获取线程的threadLocals变量,根据ThreadLocal获取数组下标再从ThreadLocalMap取出值返回。如果线程threadLocals变量为空,则会调用setInitialValue方法。

   private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

InheritableThreadLocal分析

package com.spring.learn.threadlocal;

import java.util.concurrent.CompletableFuture;

/**
 * @author : caoyu create at: 2019-11-11
 * @description:
 */
public class InheritableThreadLocalTest {

	public static void main(String s[]) {
		InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal();
		User user = new User();
		user.setName(Thread.currentThread().getName());
		inheritableThreadLocal.set(user);
		System.out.println(Thread.currentThread().getName() + ":" + user);
		CompletableFuture.allOf(CompletableFuture.runAsync(() -> {
			User user1 = (User) inheritableThreadLocal.get();
			System.out.println(Thread.currentThread().getName() + ":" + user1);
			user1.setName(Thread.currentThread().getName());
			System.out.println(Thread.currentThread().getName() + ":" + user1);
		})).join();
		System.out.println(Thread.currentThread().getName() + ":" + inheritableThreadLocal.get());
	}

	public static class User {

		private String name;

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		@Override
		public String toString() {
			return "User{" + "name='" + name + '\'' + '}';
		}

	}

}

运行结果:

main:User{name='main'}
ForkJoinPool.commonPool-worker-1:User{name='main'}
ForkJoinPool.commonPool-worker-1:User{name='ForkJoinPool.commonPool-worker-1'}
main:User{name='ForkJoinPool.commonPool-worker-1'}

我们看到main线程设置的值传递到Thread-0线程中。


package java.lang;
import java.lang.ref.*;
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    //这个方法留给子类实现
    protected T childValue(T parentValue) {
        return parentValue;
    }

    //重写getMap方法,返回的是Thread的inheritableThreadLocals引用
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    //重写createMap方法,构造的ThreadLocalMap会传给Thread的inheritableThreadLocals 变量
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

从源码上看,跟ThreadLocal不一样的无非是重写了getMap、createMap两个方法,ThreadLocalMap的引用不一样了,从逻辑上来讲,这并不能做到子线程得到父线程里的值。那秘密在那里呢?我们需要看一下Thread构造方法。

 	public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
   private void init(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 doesn't have a strong opinion of 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(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);
        //判断父线程的inheritableThreadLocals变量是否为空,不为空将值传递给子线程的inheritableThreadLocals变量
        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 */
        tid = nextThreadID();
    }

通过跟踪Thread的构造方法,我们发现只要父线程在构造子线程(调用new Thread())的时候inheritableThreadLocals变量不为空。新生成的子线程会通过ThreadLocal.createInheritedMap方法将父线程inheritableThreadLocals变量有的对象复制到子线程的inheritableThreadLocals变量上。这样就完成了线程间变量的继承与传递。

	private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];
            //这里是复制parentMap数据的逻辑
            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) {
                        //重写InheritableThreadLocal的childValue方法
                        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++;
                    }
                }
            }
        }

子线程通过继承得到的InheritableThreadLocal里的值与父线程里的InheritableThreadLocal的值具有相同的引用,如果父子线程想实现不影响各自的对象,可以重写InheritableThreadLocal的childValue方法。

TransmittableThreadLocal分析

InheritableThreadLocal可以解决在多线程中父子线程值的传递,但是在线程池中由于线程的复用InheritableThreadLocal就不在适用。引入阿里开源的TransmittableThreadLocal解决在线程池中值的传递。

package com.spring.learn.threadlocal;

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * @author : caoyu create at: 2019-11-11
 * @description:
 */
public class TransmittableThreadLocalTest {

	public static void main(String s[]) {
		InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal();
		Executor executor1 = Executors.newFixedThreadPool(1);
		for (int i = 0; i < 3; i++) {
			User user = new User();
			user.setName(Thread.currentThread().getName() + i);
			inheritableThreadLocal.set(user);
			CompletableFuture.allOf(CompletableFuture.runAsync(() -> {
				System.out.println(Thread.currentThread().getName() + ":" + inheritableThreadLocal.get());
			}, executor1)).join();
		}

		TransmittableThreadLocal transmittableThreadLocal = new TransmittableThreadLocal();
		Executor executor2 = TtlExecutors.getTtlExecutor(Executors.newFixedThreadPool(1));
		for (int i = 0; i < 3; i++) {
			User user = new User();
			user.setName(Thread.currentThread().getName() + i);
			transmittableThreadLocal.set(user);
			CompletableFuture.allOf(CompletableFuture.runAsync(() -> {
				System.out.println(Thread.currentThread().getName() + ":" + transmittableThreadLocal.get());
			}, executor2)).join();
		}

	}

	public static class User {

		private String name;

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		@Override
		public String toString() {
			return "User{" + "name='" + name + '\'' + '}';
		}

	}

}

运行结果:

pool-1-thread-1:User{name='main0'}
pool-1-thread-1:User{name='main0'}
pool-1-thread-1:User{name='main0'}
pool-2-thread-1:User{name='main0'}
pool-2-thread-1:User{name='main1'}
pool-2-thread-1:User{name='main2'}

可以发现线程池pool-1中使用InheritableThreadLocal传递参数,结果都是一样,可以发现线程池pool-2中使用TransmittableThreadLocal传递参数,参数传递符合预期。

你可能感兴趣的:(ThreadLocal,InheritableThreadLocal,TransmittableThreadLocal分析)