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

Java 並發編程核心組件自製:信號量與管道流深度實現

     編輯:Java 程式設計入門

前言:Java 早期並發基礎設施的缺失與補足

JDK 1.0/1.1 僅提供 wait()/notify() 等基礎監視器方法,缺乏高層同步構件。開發者需自行構建信號量、管道等核心並發原語。雖然 Java 5+ 引入 java.util.concurrent (Semaphore, PipedInputStream/OutputStream 等),但理解其底層實現原理、掌握自定義同步工具的編寫技巧,對於深度掌握並發編程、解決特殊場景問題、面試核心考點至關重要。本文深度解析基於 wait/notify 的計數信號量實現、JDK 管道流缺陷分析與自製管道流改進。

一、信號量:Dijkstra 的經典同步原語

1.1 核心概念

  • P 操作 (wait/acquire):計數器 -1,若結果 < 0 則阻塞當前執行緒。
  • V 操作 (signal/release):計數器 +1,若結果 ≤ 0 則喚醒一個等待執行緒。
  • 計數器初值 = 可用資源數 / 許可證數。二元信號量 (初值 1) 即互斥鎖。

1.2 基於 wait/notify 的標準實現


public class MySemaphore {
    private int permits; // 許可證計數

    public MySemaphore(int initialPermits) {
        this.permits = initialPermits;
    }

    // P 操作:獲取許可
    public synchronized void acquire() throws InterruptedException {
        permits--;
        if (permits < 0) {
            wait(); // 釋放監視器鎖,進入等待集合
        }
    }

    // V 操作:釋放許可
    public synchronized void release() {
        permits++;
        if (permits <= 0) {
            notify(); // 喚醒一個等待執行緒 (隨機/實現相關)
        }
    }

    // 非阻塞嘗試獲取
    public synchronized boolean tryAcquire() {
        if (permits > 0) {
            permits--;
            return true;
        }
        return false;
    }

    // 可用許可數 (近似值,並發下瞬間快照)
    public synchronized int availablePermits() {
        return Math.max(permits, 0);
    }
}

1.3 關鍵設計細節

  • synchronized 保證原子性:acquire/release 整體原子執行,避免檢查-修改-阻塞的競態條件。
  • wait() 釋放鎖:執行緒進入等待集合時自動釋放物件鎖,允許其他執行緒進入 release()。
  • notify() vs notifyAll():單許可信號量用 notify() 足夠;多許可或公平性要求時建議 notifyAll() 避免信號丟失 (極罕見但理論可能)。
  • 中斷處理:wait() 拋出 InterruptedException,調用者需處理 (重新中斷/拋出/轉換)。

1.4 應用範例:資源池控制


MySemaphore pool = new MySemaphore(10); // 10 個資料庫連線

// 獲取連線
pool.acquire();
Connection conn = createConnection();
try { /* 使用連線 */ } finally {
    closeConnection(conn);
    pool.release(); // 歸還許可
}

二、管道流:執行緒間資料傳輸通道

2.1 JDK 內建管道流缺陷分析

  • PipedInputStream / PipedOutputStream
  • 連接一次性:connect() 後不可斷開重連。close() 會永久標記 closed=true、清空緩衝區、斷開連接引用,**無法重置狀態重新連接**。
  • 固定緩衝區 1024 位元組:PIPE_SIZE = 1024 為 private static final,不可配置。大數據傳輸易阻塞寫入端。
  • 同步機制簡陋:依賴 wait/notifyAll 在共享緩衝區協調,無獨立鎖,擴展性差。

2.2 自製管道流:可重連、可配置緩衝區


public class MyPipedInputStream extends InputStream {
    protected byte[] buffer;
    protected int in = -1;     // 寫入指針
    protected int out = 0;     // 讀取指針
    protected boolean closedByReader = false;
    protected boolean closed = false;
    protected boolean connected = false;
    protected MyPipedOutputStream sink; // 連接的輸出流

    public static final int DEFAULT_PIPE_SIZE = 1024;
    private final int pipeSize;

    public MyPipedInputStream(int pipeSize) {
        this.pipeSize = pipeSize > 0 ? pipeSize : DEFAULT_PIPE_SIZE;
        this.buffer = new byte[this.pipeSize];
    }

    public MyPipedInputStream() { this(DEFAULT_PIPE_SIZE); }

    // 連接輸出流
    public synchronized void connect(MyPipedOutputStream src) throws IOException {
        if (connected || src.connected) throw new IOException("Already connected");
        this.sink = src;
        src.sink = this;
        connected = true;
        src.connected = true;
    }

    @Override
    public synchronized int read() throws IOException {
        if (!connected) throw new IOException("Pipe not connected");
        while (in == out && !closedByReader && !closed) {
            try { wait(); } catch (InterruptedException e) { throw new IOException("Interrupted"); }
        }
        if (closedByReader || closed) return -1;
        int b = buffer[out] & 0xFF;
        out = (out + 1) % pipeSize;
        notifyAll(); // 喚醒寫入端
        return b;
    }

    @Override
    public synchronized int read(byte[] b, int off, int len) throws IOException {
        // 批量讀取實現... (類似單字節邏輯,循環填充)
    }

    // 關鍵改進:close 重置狀態,支援重新連接
    @Override
    public synchronized void close() throws IOException {
        in = -1; out = 0;
        closedByReader = true;
        connected = false;
        closed = true;
        buffer = new byte[pipeSize]; // 重新分配緩衝區
        if (sink != null) {
            sink.sink = null;
            sink.connected = false;
            sink = null;
        }
        notifyAll();
    }

    // 供輸出流調用:寫入資料
    synchronized void receive(int b) throws IOException {
        while (in == ((out - 1 + pipeSize) % pipeSize) && !closedByReader && !closed) {
            try { wait(); } catch (InterruptedException e) { throw new IOException("Interrupted"); }
        }
        if (closedByReader || closed) throw new IOException("Pipe closed");
        buffer[in] = (byte) b;
        in = (in + 1) % pipeSize;
        notifyAll();
    }

    synchronized void receive(byte[] b, int off, int len) throws IOException {
        for (int i = 0; i < len; i++) receive(b[off + i]);
    }

    synchronized void receivedLast() {
        closed = true;
        notifyAll();
    }
}

2.3 MyPipedOutputStream 配套實現


public class MyPipedOutputStream extends OutputStream {
    private MyPipedInputStream sink;
    private boolean closed = false;
    private boolean connected = false;

    public void connect(MyPipedInputStream snk) throws IOException {
        snk.connect(this); // 委託輸入流完成雙向綁定
    }

    @Override
    public void write(int b) throws IOException {
        if (!connected || sink == null) throw new IOException("Pipe not connected");
        sink.receive(b);
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        if (!connected || sink == null) throw new IOException("Pipe not connected");
        sink.receive(b, off, len);
    }

    @Override
    public void close() throws IOException {
        if (sink != null) {
            sink.receivedLast(); // 通知讀取端資料結束
            sink.closed = true;
        }
        sink = null;
        connected = false;
        closed = true;
    }

    // 重置狀態支援重連 (需配合新的 InputStream)
    public void reset() {
        sink = null;
        connected = false;
        closed = false;
    }
}

2.4 使用範例:生產者-消費者


MyPipedInputStream in = new MyPipedInputStream(64 * 1024); // 64KB 緩衝區
MyPipedOutputStream out = new MyPipedOutputStream();
out.connect(in);

// 生產者執行緒
new Thread(() -> {
    try { 
        for (int i = 0; i < 1000; i++) out.write(("Data " + i + "\n").getBytes());
        out.close();
    } catch (IOException e) { e.printStackTrace(); }
}).start();

// 消費者執行緒 (主執行緒)
try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
    String line;
    while ((line = br.readLine()) != null) System.out.println("Received: " + line);
} catch (IOException e) { e.printStackTrace(); }

// 完成後可重置並重新連接新流 (舊流已 GC)
out.reset();
in = new MyPipedInputStream(64 * 1024);
out.connect(in);
// ... 第二輪傳輸 ...

三、現代替代方案:java.util.concurrent 管道與同步工具

結語

從 wait/notify 手工構建信號量、修復管道流缺陷,到掌握 java.util.concurrent 豐富工具箱,這條路徑完整映射了 Java 並發編程從「原語組裝」到「高層抽象」的演進歷程。**理解底層實現,是為了更好地選擇、使用、排查上層工具**。在現代開發中,首選 Semaphore、BlockingQueue、CompletableFuture、Reactive Streams;僅在極度受限環境、特殊協定實現、教學演示場景下,才考慮自製同步原語與管道流。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
需求JDK 5+ 標準方案優勢
信號量java.util.concurrent.Semaphore公平/非公平模式、可中斷、超時獲取、許可數動態調整
管道流java.io.PipedInputStream/OutputStream (改進版)JDK 9+ 修復了重連問題,緩衝區仍固定
高效執行緒間傳輸LinkedBlockingQueue, ArrayBlockingQueue無序列化開銷、支援背壓、豐富 API (poll, offer, take, drainTo)
資料流處理Reactive Streams (Flow API, Reactor, RxJava)背壓、操作符豐富、函數式組合