在JAVA开发过程中, 我们经常会使用到ThreadLocal
类,该类主要用于存储于线程相关的数据,并且数据只能够通过线程获取。其他线程是无法拿到数据的。但是有这么一个场景,父线程创建了一个子线程,希望子线程能够共享父线程ThreadLocal
中的变量数据,这应该怎么做呢?
InheritableThreadLocal
在JAVA中,有InheritableThreadLocal
这个类,该类根据名称就可以知道,其实就是可继承的ThreadLocal
. 下面我们通过实例的方式查看该类应该怎么使用。
public class InheritThreadLocalTest { public static void main(String[] args) { ThreadLocal<String> local = new ThreadLocal(); InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>(); // 父线程插入数据 Thread parent = new Thread("父线程") { @Override public void run() { local.set("父线程内容"); inheritableThreadLocal.set("可继承的变量信息..."); Thread child = new Thread("子线程") { @Override public void run() { String r = local.get(); System.out.println("子线程读取父线程内容: " + r); System.out.println("子线程读取可继承变量: " + inheritableThreadLocal.get()); inheritableThreadLocal.set("子线程内容"); } }; child.start(); try { child.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ": " + inheritableThreadLocal.get()); } }; parent.start(); } }
在上面的程序中,主要是在Thread
内部又创建了一个Thread
,一次来形成了一个父子关系。我们将上面的程序跑起来, 看看结果会是什么样子:
子线程读取父线程内容: null 子线程读取可继承变量: 可继承的变量信息... 父线程: 可继承的变量信息...
以上程序主要做了三个事情:
- 在父线程中往ThreadLocal与InheriableThreadLocal中设置绑定值
- 在子线程中分别从ThreadLocal与InheriableThreadLocal中获取父线程绑定的值
- 在子线程中向InheriableThreadLocal设置值,并由父线程获取值
通过结果可以看出, ThreadLocal
对变量是隔离的,线程间的数据是不能共享的。而InheriableThreadLocal
却可以在父子线程这种模式能够共享数据。下面我们查看在创建Thread
的时候,代码里面做了什么样的事情。
Thread源码分析
public Thread(String name) { init(null, null, name, 0); } // 执行线程初始化 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); // 判断是否集成父线程的threadlocal并且父线程的inheriablethreadlocal不为空, 则将父线程的threadlocal中的值 // 拷贝到子线程内部。 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(); }
ThreadLocal.createInheritedMap
该方法主要是实现了将InheriableThreadLocal
中的数据拷贝到新的InheriableThreadLocal
中,因此我们看看这段代码的而实现:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); } private ThreadLocalMap(ThreadLocalMap parentMap) { // 获取父线程map中的所有entry列表 Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); // 创建新的entry列表 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 Entry c = new Entry(key, value); // 计算hash值 int h = key.threadLocalHashCode & (len - 1); // 当存在有hash值的时候, 则重新生成hash,知道没有冲突位置 while (table[h] != null) h = nextIndex(h, len); // 存入entry table[h] = c; size++; } } } }
通过上面的代码分析可以得知, 在创建子线程的时候,其实会从父线程中拷贝inheriableThreadLocal
中的数据。但是这里有个点需要注意,这个拷贝只是做了浅拷贝,并不能保证数据的一致性和原子性。因此当我们ThreadLocal
中存入的是一个可变对象的时候,很可能会造成数据不一致。
数据不一致的例子?
public class InheritThreadLocalTest2 { public static void main(String[] args) { ThreadLocal<String> local = new ThreadLocal(); InheritableThreadLocal<BindValue> inheritableThreadLocal = new InheritableThreadLocal<>(); // 父线程插入数据 Thread parent = new Thread("父线程") { @Override public void run() { local.set("父线程内容"); BindValue bindValue = new BindValue(); bindValue.setName("父线程内容"); bindValue.setAge(23); inheritableThreadLocal.set(bindValue); Thread child = new Thread("子线程") { @Override public void run() { String r = local.get(); System.out.println("子线程读取父线程内容: " + r); System.out.println("子线程读取可继承变量: " + inheritableThreadLocal.get()); BindValue childVal = inheritableThreadLocal.get(); childVal.setName("子线程内容"); inheritableThreadLocal.set(childVal); } }; child.start(); try { child.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ": " + inheritableThreadLocal.get()); } }; parent.start(); } @Data static class BindValue { private String name; private Integer age; } }
我们将上面的代码再次跑一次, 查看输出的结果:
子线程读取父线程内容: null 子线程读取可继承变量: InheritThreadLocalTest2.BindValue(name=父线程内容, age=23) 父线程: InheritThreadLocalTest2.BindValue(name=子线程内容, age=23)
我们可以发现, 父线程读取的内容与子线程读取的内容是不一致的,因为子线程在执行的过程中修改了ThreadLocal
中存储的值, 因为InheriableThreadLocal是属于浅拷贝,因此子线程的修改数据会对父线程的内容产生影响
。
怎么解决?
那以上的问题怎么解决内,我觉得至少有两种方式可以解决:
- 如果父子线程在没有产生数据共享的时候,可以采用
ThreadLocal
替代InheriableThreadLocal
- 如果父子线程需要通过
ThreadLocal
进行共享数据,而父线程不希望子线程修改父线程的数据,此时我们可以将BindValue
设计成为不可变类。此时如果子线程需要修改ThreadLocal
中的值时,就需要重新创建新的BindValue
, 一次来避免互相影响。