【JAVA基础面经】进程间的通信方式

文章目录


前言

进程是系统资源分配的最小单位,每个进程拥有独立的地址空间。为了保证不同进程之间能够交换数据、同步状态、协同工作,操作系统提供了多种 IPC 机制。

1.管道(Pipe)

内核中开辟一个固定大小的缓冲区,一个进程写入,另一个进程读取。

  • 半双工(数据单向流动)
  • 只能在父子进程或兄弟进程间使用(因为有共同祖先)
  • 读取时如果没有数据会阻塞,直到有数据或所有写端关闭

Java 中的体现:ProcessBuilder 启动子进程后,可以通过 Process.getInputStream() / getOutputStream() 获得管道流,实现父进程与子进程通信。

java 复制代码
// 父进程与子进程通过管道通信示例
ProcessBuilder pb = new ProcessBuilder("grep", "java");
Process p = pb.start();
// 向子进程的标准输入写入数据
try (OutputStream os = p.getOutputStream()) {
    os.write("java is fun\npython is fun".getBytes());
}
// 读取子进程的标准输出
try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(p.getInputStream()))) {
    reader.lines().forEach(System.out::println);
}

2.命名管道(FIFO)

与普通管道的区别:在文件系统中有一个路径名,不相关的进程可以通过该文件名进行通信。

  • 特点:遵循先进先出原则,写入的数据被另一进程读取后即从内核缓冲区移除。
  • 使用场景:需要长期存在的、无亲缘关系的进程间的数据流。

Java 标准库没有直接封装 FIFO,但可以通过 JNA 或直接操作 /tmp/myfifo 等文件路径,使用 RandomAccessFile 以读写方式打开(Linux 下需要配合 mkfifo 预先创建)

3.消息队列

即消息的链表,存储在内核中,每个消息有类型和正文。

优点:

  • 解耦:发送方和接收方不需要同时运行
  • 支持多条消息,有优先级
  • 数据有边界,不像流式管道需要自己解析

缺点:消息大小通常有限制,内核空间拷贝数据有一定开销。

可以使用 Kafka、RabbitMQ 等消息中间件(用户态),或通过 java.nio.channels.Pipe 实现线程间通信(不是进程间)

4.共享内存

将同一块物理内存映射到多个进程的虚拟地址空间中,一个进程修改后,另一个进程直接可见。

  • 优点:速度最快,没有内核介入的数据拷贝(只需一次页表映射)。
  • 难点:需要同步机制(比如信号量)来避免竞争条件。

Java 实现方式包括:

  • 使用 MappedByteBuffer + FileChannel 映射文件到内存,不同进程映射同一个文件(类似 mmap)。
  • 使用第三方库(如 Chronicle Map)或 JNI 调用 POSIX 的 shmget / shmat。
java 复制代码
// Java 中使用内存映射文件模拟共享内存
RandomAccessFile file = new RandomAccessFile("shared.dat", "rw");
FileChannel channel = file.getChannel();
MappedByteBuffer buffer = channel.map(
    FileChannel.MapMode.READ_WRITE, 0, 1024);
// 进程 A 写入
buffer.putInt(0, 100);
// 进程 B 读取
int value = buffer.getInt(0);

5.信号量

一个整数计数器,支持 P(等待)和 V(释放)操作,用于控制多进程对共享资源的访问。

  • 与互斥锁的区别:信号量可以允许多个线程同时访问(计数型),互斥锁只允许一个。

Java 中的相关类:java.util.concurrent.Semaphore 用于线程间,不直接用于进程间。若需进程间信号量,需使用 FileLock 或 JNI 调用系统信号量。

6.信号(Signal)

软件中断,异步通知进程某个事件发生。常见信号:SIGKILL(强制终止)、SIGTERM(请求终止)、SIGINT(Ctrl+C)。

  • 特点:携带信息少,只用来通知事件,不传递数据。

Java 中的处理:通过 Runtime.getRuntime().addShutdownHook 捕获 SIGTERM 等信号;更细粒度的信号处理可以使用 sun.misc.Signal(非标准)或 jdk.internal.misc.Signal。

java 复制代码
// 优雅关闭示例
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    System.out.println("收到终止信号,正在释放资源...");
}));

7.Socket

通过网络协议栈(TCP/UDP)通信,可以跨主机。本地进程间通信可使用 Unix Domain Socket(比 TCP 更快,不走网络协议栈)。

  • 优点:通用性强,支持跨网络、跨语言。

Java 中的优势:java.net.Socket / ServerSocket 以及 NIO 提供了非常成熟的实现。本地通信可指定 localhost 127.0.0.1。

java 复制代码
// 服务端
ServerSocket server = new ServerSocket(8888);
Socket client = server.accept();
// 客户端
Socket socket = new Socket("localhost", 8888);

面试问题

1.共享内存为什么最快?

  • 其他IPC方式(管道、消息队列、Socket)通常需要将数据从用户空间拷贝到内核空间,再拷贝到接收方用户空间,共两次拷贝。而共享内存只在内核中建立映射,进程直接读写同一块物理内存,无需拷贝。

2.Java 中为什么很少直接用共享内存?

  • Java 的内存模型屏蔽了物理内存的直接操作,跨 JVM 的共享内存需要依赖 MappedByteBuffer 或 JNI,且需要自行处理内存布局、字节序、同步等问题,复杂度高。通常使用 Redis、Kafka 等中间件来代替。

3.管道和消息队列的核心区别?

  • 管道是无格式的字节流,没有消息边界;消息队列是有格式的独立消息,每个消息有类型和长度,接收方可以按类型读取。

4.信号量和互斥锁的区别?

  • 互斥锁是二元信号量(0/1),用于保护临界区;信号量可以大于1,允许多个资源实例被并发访问。另外,互斥锁要求同一个线程释放,信号量可以由不同进程释放。
相关推荐
henrylin99992 小时前
Hermes Agent 06. 技能、记忆与上下文文件
人工智能·python·机器学习·hermes·hermesagent
小此方2 小时前
Re:思考·重建·记录 现代C++ C++11篇 (三) 深度解构:可变参数模板、类功能演进与 STL 的新版图
开发语言·c++·stl·c++11·现代c++
小坏讲微服务2 小时前
Claude Code 终极实战指南:从终端 Agent 到 AI+Java 开发
java·开发语言·人工智能
Tisfy2 小时前
LeetCode 1848.到目标元素的最小距离:数组遍历(附python一行版)
python·leetcode·题解·遍历
ch.ju2 小时前
Java程序设计(第3版)第二章——类型转换(2)
java
爱学习的小囧2 小时前
ESXi 8.0 vSwitch与dvSwitch(分布式交换机)核心区别
服务器·开发语言·分布式·php·虚拟化
斌味代码2 小时前
NestJS 高并发实战:从异步到集群的完整方案
java·spring boot·spring
jarvisuni2 小时前
JCode添加批量测试,一键同步运行6个Claude Code!
java·服务器·前端
组合缺一2 小时前
Snack JSONPath 项目架构分析
java·架构·json·jsonpath·rfc 9535