| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 文件讀寫 | 簡單、持久化 | 慢、需序列化、同步難 | 配置共享、日誌 |
| Socket (本地/網絡) | 跨機器、標準化 | 開銷大、延遲高 | 微服務、分布式 |
| 共享內存 (Memory-Mapped File) | 極快、零拷貝、直接內存訪問 | 需同步機制、僅限同機器、Java API 較底層 | 高性能進程間數據交換、大數組共享 |
java.nio.MappedByteBuffer 將文件區域直接映射到進程虛擬地址空間,多個進程映射同一文件即實現共享內存。FileChannel 提供映射入口。
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.channels.FileLock;
public class SharedMemoryDemo {
private static final String SHARED_FILE = "shared_memory.dat";
private static final int MEMORY_SIZE = 1024; // 1KB 共享區
private static final int HEADER_SIZE = 8; // 頭部: 4字節模式 + 4字節寫鎖標誌
public static void main(String[] args) throws Exception {
// 模式 1: 寫入進程
if (args.length > 0 && args[0].equals("writer")) {
runWriter();
}
// 模式 2: 讀取進程
else if (args.length > 0 && args[0].equals("reader")) {
runReader();
}
// 默認演示: 單進程驗證
else {
initSharedFile();
runWriter();
runReader();
}
}
// 初始化共享文件
static void initSharedFile() throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(SHARED_FILE, "rw")) {
raf.setLength(MEMORY_SIZE);
}
}
// 寫入進程: 獲取文件鎖 -> 寫數據 -> 釋放鎖
static void runWriter() throws Exception {
try (RandomAccessFile raf = new RandomAccessFile(SHARED_FILE, "rw");
FileChannel fc = raf.getChannel()) {
MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_WRITE, 0, MEMORY_SIZE);
// 1. 獲取獨佔文件鎖 (非阻塞)
FileLock lock = fc.tryLock();
if (lock == null) {
System.out.println("[Writer] 無法獲取鎖,其他進程正在寫入");
return;
}
try {
System.out.println("[Writer] 獲得鎖,開始寫入...");
// 寫入頭部: 模式=1 (數據有效), 鎖標誌=1 (寫入中)
buffer.putInt(0, 1); // offset 0: mode
buffer.putInt(4, 1); // offset 4: writing flag
// 寫入業務數據 (offset 8 開始)
String data = "Hello from Writer @ " + System.currentTimeMillis();
byte[] bytes = data.getBytes("UTF-8");
buffer.position(HEADER_SIZE);
buffer.put(bytes);
// 更新頭部: 鎖標誌=0 (寫入完成)
buffer.putInt(4, 0);
buffer.force(); // 強制刷新到磁盤/內存
System.out.println("[Writer] 寫入完成: " + data);
} finally {
lock.release(); // 釋放文件鎖
System.out.println("[Writer] 釋放鎖");
}
}
}
// 讀取進程: 檢查頭部標誌 -> 讀數據
static void runReader() throws Exception {
try (RandomAccessFile raf = new RandomAccessFile(SHARED_FILE, "r");
FileChannel fc = raf.getChannel()) {
MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, MEMORY_SIZE);
// 讀取頭部
int mode = buffer.getInt(0);
int writing = buffer.getInt(4);
if (mode == 1 && writing == 0) {
buffer.position(HEADER_SIZE);
byte[] bytes = new byte[256];
int len = 0;
// 簡單讀取直到 0 字節
while (len < bytes.length && buffer.get(HEADER_SIZE + len) != 0) len++;
String data = new String(bytes, 0, len, "UTF-8");
System.out.println("[Reader] 讀取到數據: " + data);
} else if (writing == 1) {
System.out.println("[Reader] 數據正在寫入中,稍後重試");
} else {
System.out.println("[Reader] 無有效數據 (mode=" + mode + ")");
}
}
}
}
MapMode.READ_ONLY:只讀,嘗試寫入拋 ReadOnlyBufferException。MapMode.READ_WRITE:讀寫,修改最終會刷新到磁盤 (由 OS 頁面緩存管理)。MapMode.PRIVATE:寫時複製 (Copy-on-Write),修改只在內存中,不影響文件,也不被其他進程看到。
// 非阻塞嘗試獲取獨佔鎖
FileLock lock = channel.tryLock();
// 阻塞獲取
FileLock lock = channel.lock();
// 釋放
lock.release();
// 範圍鎖 (僅鎖定部分區域)
FileLock lock = channel.lock(position, size, shared);
優勢:OS 級別強制鎖,進程崩潰自動釋放,跨語言互操作 (C++/Python 同樣可用 flock/LockFileEx)。
buffer.force() 強制將緩衝區修改刷新到存儲設備。高頻寫入場景可批量 force() 或依賴 OS 延寫 (數據安全 vs 性能權衡)。
文件鎖在進程終止 (正常/異常) 時由 OS 自動釋放。但頭部標誌位可能處於不一致狀態 (如 writing=1 時崩潰)。讀取端需實現超時檢測:若 writing=1 持續超過閾值,視為寫入端異常,可嘗試強制清理 (需協商協議)。
高頻數據流 (如行情推送、日誌收集) 可在共享內存中實現環形緩衝區:
// 共享內存佈局
struct SharedRingBuffer {
int writeIndex; // 原子操作 (AtomicInteger / VarHandle)
int readIndex; // 原子操作
long[] timestamps; // 數據時間戳
byte[][] payloads; // 定長載荷槽位
// 生產者: CAS 更新 writeIndex -> 寫槽位 -> memory fence
// 消費者: 讀 readIndex -> 讀槽位 -> CAS 更新 readIndex
}
Java 中可用 VarHandle (JDK 9+) 或 AtomicInteger 配合 MappedByteBuffer 實現無鎖環形隊列。
Java 共享內存基於 MappedByteBuffer + FileChannel + FileLock 實現,提供微秒級延遲的進程間數據交換。核心難點在於同步協議設計 (鎖粒度、無鎖算法、異常恢復) 和內存佈局規劃 (頭部元數據、數據區、版本控制)。適用於同機器高性能 IPC,跨機器請用 gRPC/Netty 等網絡方案。