InheritableThreadLocal是ThreadLocal的子类,当父线程创建一个InheritableThreadLocal对象之后,InheritableThreadLocal的内容能够在这个父线程的所有子线程中共享。这个实现相当有意义。比如可以利用这个类实现多线程的共享事务。这个类实际上非常简单,在Thread源码中对其做了支持。
一、使用
1、测试
有如下测试类,在main函数的主线程中给InheritableThreadLocal设置了一个字符串。之后在这个主线程的各个子线程中进行读取。但是如果在子线程中设值,主线程是不会接收到的。如下:
public class InheritableThreadLocalTest { private static final InheritableThreadLocal<String> itl = new InheritableThreadLocal<>(); public static void main(String[] args) throws Exception{ itl.set("主线程赋值"); System.out.println("value:"+itl.get()); new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程1获取:"+itl.get()); itl.set("子线程1设值"); System.out.println("子线程1设值后再次获取:"+itl.get()); }).start(); new Thread(() -> { System.out.println("子线程2获取:"+itl.get()); }).start(); TimeUnit.SECONDS.sleep(2); System.out.println("主线程在子线程1设置之后获取值:"+itl.get()); } }
运行结果如下:
value:主线程赋值 子线程2获取:主线程赋值 子线程1获取:主线程赋值 子线程1设值后再次获取:子线程1设值 主线程在子线程1设置之后获取值:主线程赋值
二、inberitableThreadLocaly源码
/** * This class extends {@code ThreadLocal} to provide inheritance of values * from parent thread to child thread: when a child thread is created, the * child receives initial values for all inheritable thread-local variables * for which the parent has values(子线程创建时,接收所有父线程有的值作为其初始化). * Normally the child's values will be identical to the parent's; (父子线程的值一一对应) * however, the child's value can be made an arbitrary (随意的)function of the * parent's by verriding the {@code childValue} method in this class. * * <p>Inheritable thread-local variables are used in preference to * ordinary thread-local variables when the per-thread-attribute being * maintained in the variable (e.g., User ID, Transaction ID) must be * automatically transmitted to any child threads that are created. * * <p>Note: During the creation of a new {@link * Thread#Thread(ThreadGroup,Runnable,String,long,boolean) thread}, it is * possible to <i>opt out</i> of receiving initial values for inheritable * thread-local variables. * * @author Josh Bloch and Doug Lea * @see ThreadLocal * @since 1.2 */ public class InheritableThreadLocal<T> extends ThreadLocal<T> { /** * Creates an inheritable thread local variable. */ public InheritableThreadLocal() {} /** * 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); } }
在分析ThreadLocal的代码中,set和get的方法中,都有getMap和createMap。这两个方法重写了之后,就将之前的从Thread中的threadLoccals获取threadLocalMap变成了从inheritableThreadLocals获取ThreadLocalMap。 在看完这些代码之后,还是没有明白,是如何将ThreadLocalMap的内容放置到Thread的inheritableThreadLocals变量的。 因此需要进一步对Thread代码进行分析。
三、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); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { if (name == null) { throw new NullPointerException("name cannot be null"); } ... ... //与本文最关键的部分 if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); } static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }
可以看到,实际上在创建Thread的时候,会从父线程中去判断父线程的inheritableThreadLocals 是否为空,如果不为空,则调用ThreadLocal的createInheriteMap方法。
而这也解决了在之前分析ThreadLocal源码中的一个疑问。之前在ThreadLocal的初始化方法中:
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++; } } } }
上面的代码要用到 key.childValue(e.value);。这个方法。而这个方法在ThreadLocal中:
T childValue(T parentValue) { throw new UnsupportedOperationException(); }
这个方法没有返回值,只会抛出一个异常。当时的备注页明确说明了:
Construct a new map including all Inheritable ThreadLocals from given parent map. Called only by createInheritedMap.
这个批量创建ThreadLocalMap的方法只能用在createInheritedMap的时候,否则就会抛出异常。而使用createInheritedMap的时候,子类重写了childValue方法:
protected T childValue(T parentValue) { return parentValue; }
这样就不会抛出异常。这也是面向对象多态特性的一种 具体的应用。虽然其设计不如ThreadLocalMap及WeakReference方法那么让人耳目一新,但是也是我们自己在做架构设计的时候值得借鉴的。
四、总结
1.InheritableThreadLocal在线程创建的时候,从父线程中拷贝了inheritableThreadLocals,这是一个相对的深度拷贝,重建了整个ThreadLocalMap。如果Entry的value不是引用类型,那么这些Entry的值在每个Thread中互不影响。由于只copy到Entry这一级,如果Entry的value本身就是引用类型,那么将会共享
2.InheritableThreadLocal利用了面向对象的多态特性,重写了childValue、getMap和createMap方法。在Thread中对inheritableThreadLocals进行了处理。这说明,如果在Thread的基础上实现共享内存或者事务等,只能使用ThreadLocal或者InheritableThreadLocal来实现。
3.InheritableThreadLocal与ThreadLocal会有相同的内存泄漏的风险。因此需要注意对remove方法的使用。避免导致OOM或者内存泄漏。