在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, 一次来避免互相影响。