当我们在同一个线程中,想要共享变量的话,是可以直接使用ThreadLocal的,但是如果在父子线程之间,共享变量,ThreadLocal就不行了。
如以下代码,会抛出NPE:
public static ThreadLocal<Integer> sharedData = new ThreadLocal<>();
public static void main(String[] args) {
sharedData.set(0);
MyThread thread = new MyThread();
thread.start();
sharedData.set(sharedData.get() + 1);
System.out.println("sharedData in main thread: " + sharedData.get());
}
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("sharedData in child thread: " + sharedData.get());
sharedData.set(sharedData.get() + 1);
System.out.println("sharedData in child thread after increment: " + sharedData.get());
}
}
输出结果:
sharedData in child thread: null
sharedData in main thread: 1
Exception in thread "Thread-0" java.lang.NullPointerException
at com.hollis.java.bagu$MyThread.run(JavaBaguThreadLocalTest.java:30)
因为ThreadLocal 变量是为每个线程提供了独立的副本,因此不同线程之间只能访问它们自己的副本。
那么,想要实现数据共享,主要有两个办法,第一个是自己传递,第二个是借助InheritableThreadLocal
我们可以在子线程中创建一个成员变量,这样在主线程创建子线程的时候,可以给成员变量赋值,这样实现数据共享。
import java.util.concurrent.ConcurrentHashMap;
public class SharedDataExample {
public static void main(String[] args) {
ConcurrentHashMap<String, String> sharedData = new ConcurrentHashMap<String, String>();
MyThread thread = new MyThread(sharedData);
thread.start();
sharedData.put("key", "value");
System.out.println("sharedData in main thread: " + sharedData.get("key"));
}
}
class MyThread extends Thread {
ConcurrentHashMap<String, String> sharedData;
public MyThread(ConcurrentHashMap<String, String> data) {
this.sharedData = data;
}
public void run() {
sharedData.put("key", "new value");
System.out.println("sharedData in child thread: " + sharedData.get("key"));
}
}
但是一定要考虑线程安全的问题。
输出结果:
sharedData in child thread: new value
sharedData in main thread: new value
这里注意一下,有可能得到的结果是
sharedData in main thread: value
sharedData in child thread: new value
因为如果主线程先执行完,那么就是这个结果。
与 ThreadLocal 不同,InheritableThreadLocal 可以在子线程中继承父线程中的值。在创建子线程时,子线程将复制父线程中的 InheritableThreadLocal 变量。
我们把开头的示例中ThreadLocal改成InheritableThreadLocal 就可以了:
public static InheritableThreadLocal<Integer> sharedData = new InheritableThreadLocal<>();
public static void main(String[] args) {
sharedData.set(0);
MyThread thread = new MyThread();
thread.start();
sharedData.set(sharedData.get() + 1);
System.out.println("sharedData in main thread: " + sharedData.get());
}
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("sharedData in child thread: " + sharedData.get());
sharedData.set(sharedData.get() + 1);
System.out.println("sharedData in child thread after increment: " + sharedData.get());
}
}
输出结果:
sharedData in child thread: 0
sharedData in main thread: 1
sharedData in child thread after increment: 1
当我们通过Thread的构造方法创建线程的时候,会调用到java.lang.Thread#init方法,这个方法中针对InheritableThreadLocal有一些特殊的处理:
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
意思就是检查父线程中是否有inheritableThreadLocals,如果有,就把他复制到子线程的inheritableThreadLocals中!