首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 多進程並發控制:內存映射文件實現共享內存與進程同步

     編輯:Java 相關知識

Java 進程間通信 (IPC) 方案對比

方案優點缺點適用場景
文件讀寫簡單、持久化慢、需序列化、同步難配置共享、日誌
Socket (本地/網絡)跨機器、標準化開銷大、延遲高微服務、分布式
共享內存 (Memory-Mapped File)極快、零拷貝、直接內存訪問需同步機制、僅限同機器、Java API 較底層高性能進程間數據交換、大數組共享

核心類:MappedByteBuffer + FileChannel

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 + ")");
            }
        }
    }
}

關鍵技術點深度解析

1. 映射模式

  • MapMode.READ_ONLY:只讀,嘗試寫入拋 ReadOnlyBufferException。
  • MapMode.READ_WRITE:讀寫,修改最終會刷新到磁盤 (由 OS 頁面緩存管理)。
  • MapMode.PRIVATE:寫時複製 (Copy-on-Write),修改只在內存中,不影響文件,也不被其他進程看到。

2. 同步機制:文件鎖


// 非阻塞嘗試獲取獨佔鎖
FileLock lock = channel.tryLock(); 
// 阻塞獲取
FileLock lock = channel.lock(); 
// 釋放
lock.release();
// 範圍鎖 (僅鎖定部分區域)
FileLock lock = channel.lock(position, size, shared);

優勢:OS 級別強制鎖,進程崩潰自動釋放,跨語言互操作 (C++/Python 同樣可用 flock/LockFileEx)。

3. 內存一致性:force()

buffer.force() 強制將緩衝區修改刷新到存儲設備。高頻寫入場景可批量 force() 或依賴 OS 延寫 (數據安全 vs 性能權衡)。

4. 進程異常退出的鎖恢復

文件鎖在進程終止 (正常/異常) 時由 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 等網絡方案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved