目錄
多線程 Reactor 模式的核心動機
多線程演進方向
多線程 Reactor 模型結構
多線程 EchoServer 實現核心部分
Handler 的多線程化
多線程 Reactor 的三個核心點
本篇文章內容的前置知識為?單線程 Reactor 模式,如果不了解,可點擊鏈接學習
單線程 Reactor 模式-CSDN博客
多線程 Reactor 模式的核心動機
原因:單線程 Reactor 中,Reactor 線程和所有 Handler 處理器都共用同一個線程,一旦某個 Handler 執行時間較長,所有事件的處理都會被阻塞,系統無法橫向擴展。
目標:將 Reactor 和 Handler 的職責分離到多個線程中,提高并發能力,適應高連接數和重業務處理場景。
多線程演進方向
(1)升級 Handler:
將負責 IO 讀寫和業務邏輯的 Handler 放入獨立的線程池中異步處理,提高吞吐。
(2)升級 Reactor:
使用多個 Selector,創建多個子反應器(SubReactor),每個負責獨立的選擇操作。
多線程 Reactor 模型結構
客戶端請求
? ?↓
Reactor1(主反應器) ?→ ?accept handler(接收連接)
? ?↓ 分發給
Reactor2(子反應器) ?→ ?read/decode/compute/encode/write handler
Reactor 1 專職監聽新連接事件;
Reactor 2 專職監聽 IO 讀寫事件;
多個 Handler 在不同線程池中處理,互不阻塞。
多線程 EchoServer 實現核心部分
Reactor 主類
Selector[] selectors = new Selector[2];
一個 selector 專管連接接收(OP_ACCEPT);
一個 selector 專管數據讀寫(OP_READ/WRITE)。
SubReactor subReactor1 = new SubReactor(selectors[0]);
SubReactor subReactor2 = new SubReactor(selectors[1]);
為每個 selector 啟動一個線程,輪詢事件。
SubReactor 邏輯
子反應器的職責是:
負責輪詢 selector 中是否有事件發生;
若有事件觸發,就調用 dispatch() 方法,執行 handler。
SelectionKey sk = it.next();
Runnable handler = (Runnable) sk.attachment();
handler.run();
從 selectionKey 中拿出 attach 的 Handler;
直接執行 handler 的 run() 方法。
AcceptorHandler(連接處理器)
SocketChannel channel = serverSocket.accept();
new MultiThreadEchoHandler(selectors[1], channel);
接收連接后創建 IO Handler;
并將其注冊到數據讀寫 selector(selectors[1])中。
Handler 的多線程化
目標:將讀寫處理邏輯從 IO 事件輪詢線程中分離,提交到線程池異步處理。
核心實現:
static ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> asyncRun());
static ExecutorService pool = Executors.newFixedThreadPool(4);
創建一個包含 4 個線程的固定大小線程池,專門用于執行具體的業務邏輯(如數據處理、計算等)。線程池會復用這 4 個線程,避免頻繁創建銷毀線程的開銷。
pool.submit(() -> asyncRun());
將實際的業務處理邏輯(asyncRun() 方法,比如讀取數據、回顯數據的邏輯)提交到線程池執行。這樣做的核心目的是 讓 Reactor 線程(負責監聽和分發事件)不用等待業務處理完成,可以立即回去處理其他 IO 事件,避免被耗時操作阻塞。
asyncRun() 方法 的代碼思路和單線程 Reactor 模式的核心 run() 方法 代碼思路類似
單線程 Reactor 模式因 單線程瓶頸,僅適用于簡單場景;
Connection Per Thread 因 線程爆炸 問題,已被淘汰;
多線程 Reactor 模式通過 事件分離 + 線程池?解決了前兩者的缺陷
多線程 Reactor 的三個核心點
模塊 | 處理職責 | 所在線程 |
---|---|---|
Main Reactor | 監聽連接(OP_ACCEPT) | 主線程 |
Sub Reactor | 監聽讀寫事件(OP_READ / OP_WRITE) | 線程1、線程2(子線程) |
Handler(多線程) | 實際執行 IO + 業務邏輯 | 提交給線程池異步執行 |
優勢對比
模型 | 是否并發 | 是否線程隔離 | 是否支持線程池 |
---|---|---|---|
單線程 Reactor | 否 | 否 | 否 |
多線程 Reactor | 是 | 是 | 是 |
Reactor 模式的優缺點
一、Reactor 模式與其他模式對比
(1)Reactor 模式 vs 生產者-消費者模式
相似之處:
二者都具有事件驅動特性。
在生產者-消費者模型中:
生產者產生任務(事件)并放入一個隊列。
消費者從隊列中拉取(pull)任務并處理。
Reactor 模式下:
客戶端連接、讀寫事件相當于生產者。
Selector 檢查事件是否就緒,相當于從“系統緩沖隊列”中拉取事件。
Handler 負責處理事件,相當于消費者。
不同之處:
生產者-消費者模型是基于「消息隊列」的顯式交互;
Reactor 模式是基于 IO 多路復用的「查詢式機制」(如 select、epoll)
Reactor 沒有明確的隊列,而是通過系統底層緩存區查詢到事件,再立即由 Dispatcher 分發給對應 Handler 處理。
(2)Reactor 模式 vs 觀察者模式
相似之處:
兩者都體現了事件監聽 + 響應處理的思想。
Reactor 中,Selector 檢測到事件 → Dispatcher 分發給對應 Handler。
觀察者模式中,主題 Topic 發生變化 → 通知所有訂閱該主題的觀察者 Observer。
都涉及“注冊監聽 + 回調響應”的機制。
不同之處:
觀察者模式允許一個事件被多個觀察者處理(一對多)
而 Reactor 模式中,一個 IO 事件只能交給一個 Handler 實例處理(一對一)
即:事件和處理器的綁定是唯一的,Handler 和 SelectionKey 是一一對應的。
二、Reactor 模式的優點
高響應性:
單線程模式:通過 非阻塞 IO + 事件驅動,避免了傳統阻塞 IO 中 一個連接阻塞導致全系統卡住?的問題(僅當 Handler 無阻塞操作時有效)
多線程模式:進一步通過 Reactor 線程與業務線程分離,即使某個業務邏輯阻塞,也不會影響 Reactor 線程處理其他連接,響應性更優。
編程簡單:
單線程模式:天然無多線程共享數據問題,不需要鎖和同步,邏輯最簡潔。
多線程模式:通過 職責分離(Reactor 線程管 IO,業務線程管邏輯),減少了共享數據的場景,相比 一連接一線程?模式,鎖和同步的復雜度更低。
良好的可擴展性:
多線程模式:可通過增加 Reactor 線程數(利用多核 CPU)、擴大業務線程池(處理更多并發業務)顯著提升吞吐量,擴展性更強。
三、Reactor 模式的缺點
模型實現較復雜:
相較于簡單的阻塞 IO,Reactor 涉及 Selector 注冊、事件派發、狀態管理,門檻較高;
不易調試,錯誤不易定位。
強依賴系統支持:
依賴于操作系統提供的高效 IO 多路復用機制(如 Linux 的 epoll);
若底層不支持 IO 復用,性能優勢無法體現,甚至可能適得其反。
Handler 內部阻塞會影響整體反應能力:
如果某個 Handler 內部業務邏輯阻塞太久(比如讀取大文件、等待數據庫響應),會導致整個 Selector 線程卡住,從而影響其他 IO 事件的派發,形成偽非阻塞。
模式 | 特點 | 是否規避偽非阻塞? |
---|---|---|
多 Reactor + 同步 Handler | 多線程負責 Selector,Handler 仍由 Selector 線程執行 | 不能規避 |
多 Reactor + 異步 Handler(線程池) | Selector 線程只分發事件,Handler 放入線程池處理 | 能完全規避 |
同步異步handler主要是看它的run方法里面有沒有包含耗時/阻塞操作,比如read() / write()
偽非阻塞?指的是表面上使用了非阻塞 IO(如 NIO 的 SocketChannel 配置為非阻塞模式)和 Reactor 事件驅動模型,但由于某個環節(通常是 Handler 處理邏輯)出現阻塞,導致整個 Reactor 線程被卡住,最終失去了非阻塞模式應有的并發處理能力。