“在操作系统中,进程间的通讯有4种,分别是信号量、管道、消息队列、共享内存。通过这4种方式,多个线程就能相互传输数据。本篇主要介绍这4种方式在Java层面是如何实现的
”

上图是CSAPP书中对于IPC(Interprocess Communication)实现方式的描述。虽然书中描写的是进程间的通讯而不是线程,但Java将进程间的通讯思想也运用在了Java线程中。
共享内存、信号量、消息队列
共享内存的实现是最好理解的一种,可能平时在编写多线程代码时都忽略了这一点。多个线程对同一个Java对象操作就是操作共享内存。
信号量是实现线程间同步的手段,控制进入管程的线程数量,关于线程同步可以看<Java线程同步>。虽然JUC包下有个Semaphore工具类,翻译过来也叫信号量, 但操作系统的信号量的涉及范围会更加广,Java的 wait() .notify()都算是信号量的的实现。简单点说,就是涉及到线程同步的都属于信号量。
一说到消息队列很多人第一反应就是RabbitMQ这些中间件,确实采用这些中间件同样可以达到Java线程通讯,一个线程push数据进MQ,另一个监听MQ并取数据。但其实JUC包下的那些线程安全的阻塞队列都可以视为消息队列。我们可以采用生产消费模式,一个线程制造数据push进阻塞队列中,另一个线程从阻塞队列take数据消费。
管道
Linux系统的”|“操作符就是利用了管道进行进程间的通讯。比如下面查询当前系统中正在运行的Redis的进程信息的命令,管道将 ps 进程输出的数据,做为 grep 命令的输入数据。
ps -ef | grep redis
Java提供了PipedOutputStream、PipedInputStream这两个类实现通过管道的方式进行IPC
@Slf4j(topic = "c.PipeStreamDemo")
public class PipeStreamDemo {
public static void main(String[] args) {
try {
// 创建线程对象Sender
Sender sender = new Sender();
// 创建线程对象Receiver
Receiver receiver = new Receiver();
// 写入管道
PipedOutputStream out = sender.getOutputStream();
// 从管道读出数据
PipedInputStream in = receiver.getInputStream();
// 连通输出输入Pipe
out.connect(in);
// 启动线程
sender.start();
receiver.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Slf4j(topic = "c.Sender")
class Sender extends Thread {
private PipedOutputStream out = new PipedOutputStream();
public PipedOutputStream getOutputStream() {
return out;
}
public void run() {
Thread.currentThread().setName("发送线程");
String s = "Receiver,你好!";
try {
log.info("发送了:"+s);
out.write( s.getBytes());
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Slf4j(topic = "c.Receiver")
class Receiver extends Thread {
private PipedInputStream in = new PipedInputStream();
public PipedInputStream getInputStream() {
return in;
}
public void run() {
Thread.currentThread().setName("接收线程");
String s;
byte[] b = new byte[1024];
try {
int len = in.read(b);
s = new String(b, 0, len);
log.info("收到了以下信息:" + s);
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
控制台输出:
c.Sender [发送线程] - 发送了:Receiver,你好!
c.Receiver [接收线程] - 收到了以下信息:Receiver,你好!
Java通过管道进行线程间的通讯方式很像网络通讯,只不过Java的IPC不需要制定IP、端口只需将PipedOutputStream和PipedInputStream调用.connect()方法连通即可。
总结
CSAPP描述IPC是以进程的方式描述的,站在计算机中各个不同的程序间数据相传输、操作的角度设计了4种通讯方式。而Java的JVM就好比一台计算机,运行在JVM中的各个线程就好比真实机器上的进程,因此Java线程间的通讯处处可以看到IPC的身影。




