Inter-Process Communication (IPC)
IntermediateIPC mechanisms — pipes, message queues, shared memory, and sockets — allow processes to exchange data, each with different trade-offs in speed, complexity, and synchronization requirements.
Overview
Since processes have isolated address spaces, the OS provides IPC mechanisms to let them communicate. Pipes (anonymous) connect parent and child processes via a unidirectional byte stream — the simplest form. Named pipes (FIFOs) allow unrelated processes to communicate. Message queues are kernel-managed linked lists of messages — asynchronous and persist until explicitly deleted. Shared memory is the fastest IPC: two processes map the same physical pages into their address spaces. Because no data is copied, it is blazing fast but requires explicit synchronization (semaphores, mutexes). Sockets work across network boundaries and are the foundation of all client-server communication. Java supports IPC via ProcessBuilder I/O redirection, Java NIO sockets, and Unix domain sockets (Java 16+).
Pipes: Parent-Child Communication
An anonymous pipe is a unidirectional byte channel created by the kernel. In Java, ProcessBuilder's I/O streams are exactly pipes under the hood — the parent reads from the child's stdout pipe. Named pipes (FIFOs) are filesystem objects that allow unrelated processes to communicate, but still unidirectional per pipe.
// Java pipe via ProcessBuilder — parent reads child's stdout
ProcessBuilder pb = new ProcessBuilder("cat", "/etc/hostname");
pb.redirectErrorStream(true);
Process child = pb.start();
// parent reads from child's stdout pipe
try (var reader = new BufferedReader(
new InputStreamReader(child.getInputStream()))) {
reader.lines().forEach(line -> System.out.println("Got: " + line));
}
child.waitFor();
// Java Pipe streams between threads (in-process analog)
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
Thread writer = new Thread(() -> {
try { pos.write("hello pipe".getBytes()); pos.close(); }
catch (IOException e) { e.printStackTrace(); }
});
Thread reader2 = new Thread(() -> {
try { System.out.println(new String(pis.readAllBytes())); }
catch (IOException e) { e.printStackTrace(); }
});
writer.start(); reader2.start();
writer.join(); reader2.join();Sockets: Network and Unix Domain IPC
Sockets are the most flexible IPC — they work between processes on the same machine (Unix domain sockets) or across a network (TCP/UDP). Unix domain sockets (Java 16+) are significantly faster than TCP loopback because there is no network stack overhead. They are used by Docker, PostgreSQL, and Redis for local IPC.
// Java 16+ Unix domain socket — fast local IPC (no TCP overhead)
Path socketPath = Path.of("/tmp/my-ipc.sock");
// Server side
Thread server = Thread.ofVirtual().start(() -> {
try (ServerSocketChannel ssc = ServerSocketChannel.open(StandardProtocolFamily.UNIX)) {
ssc.bind(UnixDomainSocketAddress.of(socketPath));
SocketChannel client = ssc.accept();
ByteBuffer buf = ByteBuffer.allocate(256);
client.read(buf);
System.out.println("Server received: " + new String(buf.array()).trim());
} catch (IOException e) { e.printStackTrace(); }
});
Thread.sleep(100); // let server bind
// Client side
try (SocketChannel sc = SocketChannel.open(UnixDomainSocketAddress.of(socketPath))) {
sc.write(ByteBuffer.wrap("hello IPC".getBytes()));
}
server.join();
Files.deleteIfExists(socketPath);Shared Memory vs Message Queues
Shared memory is the fastest IPC — zero copy, two processes read/write the same memory pages. But it requires explicit synchronization (semaphores or mutexes) to avoid race conditions. Message queues copy data through the kernel but are inherently synchronized — the kernel serializes access. For high-throughput data pipelines, shared memory wins. For coordinated task dispatch, message queues are safer.
// IPC mechanisms comparison:
// ┌─────────────────┬──────────┬──────────────┬──────────────────┐
// │ Mechanism │ Speed │ Sync needed? │ Cross-machine? │
// ├─────────────────┼──────────┼──────────────┼──────────────────┤
// │ Pipe │ Medium │ No (FIFO) │ No │
// │ Named Pipe │ Medium │ No (FIFO) │ No │
// │ Message Queue │ Medium │ No (kernel) │ No │
// │ Shared Memory │ FAST │ YES │ No │
// │ Unix Socket │ Fast │ No (stream) │ No │
// │ TCP Socket │ Slower │ No (stream) │ YES │
// └─────────────────┴──────────┴──────────────┴──────────────────┘
// Java shared memory via MappedByteBuffer (memory-mapped file)
try (RandomAccessFile raf = new RandomAccessFile("/tmp/shared.mem", "rw");
FileChannel fc = raf.getChannel()) {
MappedByteBuffer sharedMem = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
sharedMem.putInt(0, 42); // write to shared memory
System.out.println(sharedMem.getInt(0)); // read back — another process can see this
}Key Points to Remember
- 1Anonymous pipes are unidirectional byte streams for parent-child communication.
- 2Named pipes (FIFOs) allow unrelated processes to communicate via the filesystem.
- 3Message queues are kernel-managed, asynchronous, and self-synchronized.
- 4Shared memory is the fastest IPC — zero copy — but requires explicit synchronization.
- 5Unix domain sockets (Java 16+) are faster than TCP loopback for same-machine IPC.
- 6TCP sockets are the only IPC mechanism that works across machines.
Interview Questions
Sign in to ask AriaWhat is the fastest form of IPC and why?
Why does shared memory require explicit synchronization while message queues do not?
What is the difference between an anonymous pipe and a named pipe?
How would you implement IPC between two Java microservices on the same host with minimal latency?
Ask Aria about Inter-Process Communication (IPC)
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.