【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,允许多个资源实例被并发访问。另外,互斥锁要求同一个线程释放,信号量可以由不同进程释放。
相关推荐
ZJU_统一阿萨姆16 小时前
【推理优化进阶】图编译与运行时:动态形状、CUDA Graph 与内存规划
开发语言·人工智能·语言模型·架构·开源
@呱呱爱学习16 小时前
C++大成之路_ STL容器_ Vector
开发语言·c++
vHelios16 小时前
【电商项目】商品搜索开发复盘(1):根据需求拆解搜索接口的设计逻辑
java·微服务·es
码匠许师傅16 小时前
【C++ 面试真题】19. 聊聊 C++ 的迭代器
开发语言·c++·面试
AI情绪识别开源16 小时前
检信ALLEMOTION VibrationAI 2.4.0 12维度情绪识别开源源代码
开发语言·python
Dovis(誓平步青云)16 小时前
DevEco Studio 6.1.1 Windows 安装实录:从下载校验到首次启动
android·开发语言·数据库·人工智能·windows·harmonyos
qeen8717 小时前
【数据结构】红黑树的算法原理解析与实现
开发语言·数据结构·c++·算法·红黑树
极创信息17 小时前
国产化信创适配认证高频术语:信创适配、软件自主可控、国产化率、代码溯源率、代码自主率、代码开源率是什么?
java·python·struts·eclipse·开源·php·hibernate
Data_Journal17 小时前
什么是 CAPTCHA,它是如何工作的?
java·大数据·服务器·前端·数据库
mqiqe17 小时前
响应式流中的错误处理:Project Reactor 异常治理全体系
java·架构