线程数据通信
- 使用共享变量/对象
- 父子线程通信
- Exchanger
- 管道
我们知道Java每个线程之间是数据隔离的,那在多线程环境下,两个线程之间,如何进行数据传输呢
下面我们以main线程中新起一个子线程的方式,来模拟两个线程之间数据通信的场景。
使用共享变量/对象
private static String name = "张三";
public static void main(String[] args) {
System.out.println("主线程:name :" + name);
new Thread(() -> {
System.out.println("子线程:name :" + name);
}).start();
}
当多个线程读取同一个变量/对象时,当其中一个线程修改了 此变量/对象,下次另一个线程在读取时便可以获取到更改后的值。
但是需要注意:
- 当多个线程共享同一个数据时,需要考虑并发修改的情况,每一次修改只能由一个线程操作,此时需要对数据进行加锁
- 每个线程修改数据,实际修改的是本线程栈中的数据副本,应该使用volatile让其修改对其他线程可见,避免其他线程依旧读取数据副本
父子线程通信
如果两个线程之间是父子关系,可以使用InheritableThreadLocal将父线程中存储的信息传给子线程:
public class MyThreadData {
public static ThreadLocal<String> local = new ThreadLocal<>();
public static InheritableThreadLocal<String> inhLocal = new InheritableThreadLocal<>();
public static String getLocal() {
return local.get();
}
public static String getinhLocal() {
return inhLocal.get();
}
}
我们定义一个MyThreadData类,里面有两个变量:ThreadLocal,和InheritableThreadLocal,下面我们对这两种local做下测试
主线程中分别往ThreadLocal和InheritableThreadLocal中存入name"张三"和"李四"。
我们在子线程中通过读取ThreadLocal并没有获取到信息,但是通过读取InheritableThreadLocal则可以获取到信息。
这是因为ThreadLocal是线程私有的,必须当前线程才能访问到数据,而InheritableThreadLocal则可以将数据传递给子线程。为什么会这样呢?我们来看下一个线程启动时做了哪些事件:
我们定位到Thread这个类的init方法,可以看到在线程create的时候,会去判断parent.inheritableThreadLocals是否为null,这个parent自然就是我们的主线程了,在不为null的情况下会去调用ThreadLocal.createInheritedMap()方法,入参便是主线程的inheritableThreadLocals对象。我们再看下createInheritedMap这个方法做了什么事情:
这边我们可以看到,最终会去遍历父线程inheritableThreadLocals里面的数据,在子线程中依据父线程数据创建出一个新的entry对象。这样在子线程中自然就持有了父线程数据,且之后两套数据相互独立。
Exchanger
我们也可以使用JDK提供的Exchanger进行数据交换:
通过demo我们可以看到两个线程之间,相互交换了数据。
Exchanger ,可以看成是有两个格子,当两个格子被填满之后,进行数据交换。但是需要注意的是,exchange方法,可以称为交换点,这个方法是阻塞的。如果一个线程已经执行到了exchange方法,会阻塞在这边等待另一个线程也执行exchange方法。
当然我们也可以设置超时时间:
管道
需要定义管道的输入流和输出流,主线程向管道中写入"hello",另一个线程从管道中读取出“hello”。