【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,允许多个资源实例被并发访问。另外,互斥锁要求同一个线程释放,信号量可以由不同进程释放。
相关推荐
李可以量化1 分钟前
miniqmt 行情的备选方案 ---tdxrs(上)
python
后台模板学习5 分钟前
学习的心态高频面试题
java·数据库·学习
平头哥技术团队18 分钟前
Day 16 | 用 ul、ol、li 一个标签,把散资料收成有序的技能清单页
开发语言·前端·html·html5
Cenxi27 分钟前
f-string 完全手册:60 题从入门到实战
python
统计学小王子27 分钟前
数学建模国赛倒计时 2 天 ——《统计模型精讲(广义线性模型 R 语言实例 + 分析)》
开发语言·数学建模·r语言
用户31268748772029 分钟前
线程池到底怎么管理线程的?从 ThreadPoolExecutor 到拒绝策略全链路拆解
java
2601_9622998831 分钟前
Python编写Linux命令指南
linux·python·开发·命令·指南
此冬歌咏34 分钟前
自动化服务器运维监控系统(python+shell)
linux·运维·服务器·开发语言·python·自动化
zhanghaha131436 分钟前
Python进阶教程:23_Scrapy 爬虫框架 零基础超详细教程
python·信息可视化
cpolar技术支持44 分钟前
Kafka Streams 窗口统计怎么验收:本地跑订单流聚合,用 cpolar 给同事看只读结果页
java·docker·kafka·cpolar·kafka streams