1. NIO
閱讀本節(jié)前柜蜈,請(qǐng)先閱讀我的NIO基礎(chǔ)文章:http://www.reibang.com/nb/8788241
NIO是Java中的一種同步非阻塞IO,NIO是面向buffer的非阻塞IO指巡。其中最重要的的三個(gè)核心概念是:Channel淑履,Buffer和Selector。
Channel
Channel類似于BIO中的流藻雪,可以從中讀取或者寫入數(shù)據(jù)秘噪。但它和流有以下區(qū)別:
- Channel是雙向的,既可以讀又可以寫勉耀,而流是單向的缆娃。
- Channel可以進(jìn)行異步的讀寫。
- 對(duì)Channel的讀寫必須通過(guò)buffer對(duì)象瑰排。
在Java NIO中Channel主要有如下幾種類型:
- FileChannel:從文件讀取數(shù)據(jù)的
- DatagramChannel:讀寫UDP網(wǎng)絡(luò)協(xié)議數(shù)據(jù)
- SocketChannel:讀寫TCP網(wǎng)絡(luò)協(xié)議數(shù)據(jù)
- ServerSocketChannel:可以監(jiān)聽(tīng)TCP連接
Buffer
Buffer是NIO中用于存放待讀寫數(shù)據(jù)的容器。數(shù)據(jù)總是從Channel中讀取到Buffer中暖侨,或者從Buffer中寫入到Channel中椭住。
常見(jiàn)Buffer的實(shí)現(xiàn)類包括:ByteBuffer,CharBuffer字逗,DoubleBuffer京郑,F(xiàn)loatBuffer,IntBuffer葫掉,LongBuffer些举,ShortBuffer等。
其中ByteBuffer又包含兩個(gè)實(shí)現(xiàn)類:HeapBuffer和MappedByteBuffer(以及其實(shí)現(xiàn)類DirectBuffer)俭厚。
HeapBuffer這種緩沖區(qū)是分配在堆上面的户魏,而DirectBuffer則是直接指向了一塊堆外直接內(nèi)存。
零拷貝技術(shù)
下圖展示了一個(gè)IO讀寫流程:
- DMA
read
讀取磁盤文件內(nèi)容到內(nèi)核緩沖區(qū) - 拷貝內(nèi)核緩沖區(qū)數(shù)據(jù)到應(yīng)用進(jìn)程緩沖區(qū)(內(nèi)核態(tài)和用戶態(tài)的切換)
- 從應(yīng)用進(jìn)程緩沖區(qū)copy數(shù)據(jù)到socket緩沖區(qū)(內(nèi)核態(tài)和用戶態(tài)的切換)
-
DMA copy
給網(wǎng)卡發(fā)送
可以清楚得看到,上述IO流程包含兩次用戶態(tài)和內(nèi)核態(tài)上下文切換叼丑,在高并發(fā)場(chǎng)景下关翎,這些會(huì)很致命。因此鸠信,Linux提出了零拷貝的概念:即避免用戶態(tài)和內(nèi)核態(tài)的切換纵寝,直接在內(nèi)核中進(jìn)行數(shù)據(jù)傳遞。Linux提供了兩個(gè)函數(shù)mmap
和sendfile
來(lái)實(shí)現(xiàn)零拷貝:
- mmap: 內(nèi)存映射文件星立,即將文件的一段直接映射到內(nèi)存爽茴,內(nèi)核和應(yīng)用進(jìn)程共用同一塊內(nèi)存地址
- sendfile: 從上圖的內(nèi)核緩沖區(qū)直接復(fù)制到socket緩沖區(qū), 不需要向應(yīng)用進(jìn)程緩沖區(qū)拷貝
mmap
傳統(tǒng)的IO操作都是在內(nèi)核準(zhǔn)備好數(shù)據(jù)后,將數(shù)據(jù)從內(nèi)核中拷貝一份到用戶空間中绰垂。而直接內(nèi)存(mmap技術(shù))將文件直接映射到內(nèi)核空間的內(nèi)存室奏,返回一個(gè)操作地址(address),省去了內(nèi)核空間拷貝到用戶空間這一步操作辕坝。如下圖所示:
在NIO中窍奋,MappedByteBuffer則對(duì)應(yīng)著mmap
技術(shù)。下面是MappedByteBuffer的使用例子:
FileChannel f1 = new FileInputStream(file1).getChannel();
// FileOutputStream打開(kāi)的FileChannel只能寫入
FileChannel f2 = new FileOutputStream(file2).getChannel();)
// 將file1的數(shù)據(jù)全部映射成ByteBuffer
MappedByteBuffer mbb = f1.map(MapMode.READ_ONLY, 0, file.length());
// 將buffer里的數(shù)據(jù)寫入到file2中
f2.write(mbb);
mbb.clear();
HeapBuffer的數(shù)據(jù)結(jié)構(gòu)類似于:
public Class HeapBuffer {
byte[] data;
int position, limit, int capacity;
}
而DirectBuffer則直接指向一個(gè)內(nèi)存地址:
public Class DirectBuffer {
long address;
int position, limit, int capacity;
}
當(dāng)我們把一個(gè)Direct Buffer寫入Channel的時(shí)候酱畅,就好比是“內(nèi)核緩沖區(qū)”的內(nèi)容直接寫入了Channel琳袄,這樣顯然快了,減少了數(shù)據(jù)拷貝纺酸。而當(dāng)我們把一個(gè)Heap Buffer寫入Channel的時(shí)候窖逗,實(shí)際上底層實(shí)現(xiàn)會(huì)先構(gòu)建一個(gè)臨時(shí)的Direct Buffer,然后把Heap Buffer的內(nèi)容復(fù)制到這個(gè)臨時(shí)的Direct Buffer上餐蔬,再把這個(gè)Direct Buffer寫出去碎紊。當(dāng)然,如果我們多次調(diào)用write方法樊诺,把一個(gè)Heap Buffer寫入Channel仗考,底層實(shí)現(xiàn)可以重復(fù)使用臨時(shí)的Direct Buffer,這樣不至于因?yàn)轭l繁地創(chuàng)建和銷毀Direct Buffer影響性能词爬。
Direct Buffer創(chuàng)建和銷毀的代價(jià)很高秃嗜,所以要用在盡可能重用的地方。 比如周期長(zhǎng)傳輸文件大采用Direct Buffer顿膨,不然一般情況下就直接用heap buffer 就好锅锨。
sendfile
sendfile
不存在內(nèi)存映射, 同時(shí)保留了mmap
的不需要來(lái)回拷貝優(yōu)點(diǎn),適用于應(yīng)用進(jìn)程不需要對(duì)讀取的數(shù)據(jù)做任何處理的場(chǎng)景恋沃。如圖:
Java中Channel.transferTo(Channel destination)
對(duì)應(yīng)著sendfile
技術(shù)必搞。
Selector
Selector用于監(jiān)聽(tīng)多個(gè)Channel的事件。
2. Reactor模型
Reactor模型中主要有三種角色:
- Reactor:內(nèi)部封裝了一個(gè)selector囊咏,循環(huán)調(diào)用select方法獲得就緒channel恕洲。然后將就緒channel dispatch給對(duì)應(yīng)handler執(zhí)行真的讀寫邏輯塔橡。
- Acceptor:監(jiān)聽(tīng)客戶端連接,并為客戶端的SocketChannel向Reactor注冊(cè)對(duì)應(yīng)的handler研侣。
- Handlers:真正執(zhí)行非阻塞讀/寫任務(wù)邏輯谱邪。
Reactor模型從復(fù)雜程度又可以分為三種:?jiǎn)蜶eactor單線程模型,單Reactor多線程模型和多Reactor多線程模型庶诡。
2.1 單Reactor單線程模型
下面是其實(shí)現(xiàn):
/**
* 等待事件到來(lái)惦银,分發(fā)事件處理
*/
class Reactor implements Runnable {
?
private Reactor() throws Exception {
?
SelectionKey sk =
serverSocket.register(selector,
SelectionKey.OP_ACCEPT);
// attach Acceptor 處理新連接
sk.attach(new Acceptor());
}
?
public void run() {
try {
while (!Thread.interrupted()) {
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
while (it.hasNext()) {
it.remove();
//分發(fā)事件處理
dispatch((SelectionKey) (it.next()));
}
}
} catch (IOException ex) {
//do something
}
}
?
void dispatch(SelectionKey k) {
// 若是連接事件獲取是acceptor
// 若是IO讀寫事件獲取是handler
Runnable runnable = (Runnable) (k.attachment());
if (runnable != null) {
runnable.run();
}
}
?
}
/**
* 連接事件就緒,處理連接事件
*/
class Acceptor implements Runnable {
@Override
public void run() {
try {
SocketChannel c = serverSocket.accept();
if (c != null) {// 注冊(cè)讀寫
new Handler(c, selector);
}
} catch (Exception e) {
?
}
}
}
/**
* 處理讀寫業(yè)務(wù)邏輯
*/
class Handler implements Runnable {
public static final int READING = 0, WRITING = 1;
int state;
final SocketChannel socket;
final SelectionKey sk;
?
public Handler(SocketChannel socket, Selector sl) throws Exception {
this.state = READING;
this.socket = socket;
sk = socket.register(selector, SelectionKey.OP_READ);
sk.attach(this);
socket.configureBlocking(false);
}
?
@Override
public void run() {
if (state == READING) {
read();
} else if (state == WRITING) {
write();
}
}
?
private void read() {
process();
//下一步處理寫事件
sk.interestOps(SelectionKey.OP_WRITE);
this.state = WRITING;
}
?
private void write() {
process();
//下一步處理讀事件
sk.interestOps(SelectionKey.OP_READ);
this.state = READING;
}
?
/**
* task 業(yè)務(wù)處理
*/
public void process() {
//do something
}
}
這是最基本的單Reactor單線程模型。其中Reactor線程末誓,負(fù)責(zé)多路分離Socket扯俱,有新的客戶端連接觸發(fā)accept事件之后,Reactor交由Acceptor進(jìn)行處理喇澡。當(dāng)有IO讀寫事件就緒后則交給Hanlder 處理迅栅。
Acceptor主要任務(wù)就是構(gòu)建Handler ,在獲取到和client相關(guān)的SocketChannel之后 晴玖,注冊(cè)讀寫事件到Reactor(Selector)上读存,并綁定對(duì)應(yīng)的Hanlder。對(duì)應(yīng)的SocketChannel有讀寫事件之后呕屎,Reactor再交給對(duì)應(yīng)的Hanlder進(jìn)行處理让簿。
2.2 單Reactor多線程模型
單Reactor多線程模型,在單Reactor的基礎(chǔ)上秀睛,增加了一個(gè)Worker線程池尔当,用于Handler的執(zhí)行。
/**
* 多線程處理讀寫業(yè)務(wù)邏輯
*/
class MultiThreadHandler implements Runnable {
public static final int READING = 0, WRITING = 1;
int state;
final SocketChannel socket;
final SelectionKey sk;
?
//多線程處理業(yè)務(wù)邏輯
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
?
?
public MultiThreadHandler(SocketChannel socket, Selector sl) throws Exception {
this.state = READING;
this.socket = socket;
sk = socket.register(selector, SelectionKey.OP_READ);
sk.attach(this);
socket.configureBlocking(false);
}
?
@Override
public void run() {
if (state == READING) {
read();
} else if (state == WRITING) {
write();
}
}
?
private void read() {
//任務(wù)異步處理
executorService.submit(() -> process());
?
//下一步處理寫事件
sk.interestOps(SelectionKey.OP_WRITE);
this.state = WRITING;
}
?
private void write() {
//任務(wù)異步處理
executorService.submit(() -> process());
?
//下一步處理讀事件
sk.interestOps(SelectionKey.OP_READ);
this.state = READING;
}
?
/**
* task 業(yè)務(wù)處理
*/
public void process() {
//do IO ,task,queue something
}
}
相對(duì)于第一種單線程的模式來(lái)說(shuō)蹂安,在處理業(yè)務(wù)邏輯椭迎,也就是獲取到IO的讀寫事件之后,交由線程池來(lái)處理田盈,這樣可以減小主reactor的性能開(kāi)銷畜号,從而更專注的做事件分發(fā)工作了,從而提升整個(gè)應(yīng)用的吞吐允瞧。
2.3 多Reactor多線程模式
相比較于第二種模型简软,多Reactor多線程模式將Reactor分為兩種:
- MainReactor:負(fù)責(zé)監(jiān)聽(tīng)socket連接,用來(lái)處理新連接的建立瓷式,將建立的socketChannel指定注冊(cè)給SubReactor。
- SubReactor:維護(hù)自己的selector, 基于MainReactor注冊(cè)的socketChannel语泽,監(jiān)聽(tīng)讀寫就緒事件贸典,讀寫就緒后將Handler扔給worker線程池來(lái)完成。
/**
* 多work 連接事件Acceptor,處理連接事件
*/
class MultiWorkThreadAcceptor implements Runnable {
?
// cpu線程數(shù)相同多work線程
int workCount =Runtime.getRuntime().availableProcessors();
SubReactor[] workThreadHandlers = new SubReactor[workCount];
volatile int nextHandler = 0;
?
public MultiWorkThreadAcceptor() {
this.init();
}
?
public void init() {
nextHandler = 0;
for (int i = 0; i < workThreadHandlers.length; i++) {
try {
workThreadHandlers[i] = new SubReactor();
} catch (Exception e) {
}
?
}
}
?
@Override
public void run() {
try {
SocketChannel c = serverSocket.accept();
if (c != null) {// 注冊(cè)讀寫
synchronized (c) {
// 順序獲取SubReactor踱卵,然后注冊(cè)channel
SubReactor work = workThreadHandlers[nextHandler];
work.registerChannel(c);
nextHandler++;
if (nextHandler >= workThreadHandlers.length) {
nextHandler = 0;
}
}
}
} catch (Exception e) {
}
}
}
/**
* 多work線程處理讀寫業(yè)務(wù)邏輯
*/
class SubReactor implements Runnable {
final Selector mySelector;
?
//多線程處理業(yè)務(wù)邏輯
int workCount =Runtime.getRuntime().availableProcessors();
ExecutorService executorService = Executors.newFixedThreadPool(workCount);
?
?
public SubReactor() throws Exception {
// 每個(gè)SubReactor 一個(gè)selector
this.mySelector = SelectorProvider.provider().openSelector();
}
?
/**
* 注冊(cè)chanel
*
* @param sc
* @throws Exception
*/
public void registerChannel(SocketChannel sc) throws Exception {
sc.register(mySelector, SelectionKey.OP_READ | SelectionKey.OP_CONNECT);
}
?
@Override
public void run() {
while (true) {
try {
//每個(gè)SubReactor 自己做事件分派處理讀寫事件
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isReadable()) {
read();
} else if (key.isWritable()) {
write();
}
}
?
} catch (Exception e) {
?
}
}
}
?
private void read() {
//任務(wù)異步處理
executorService.submit(() -> process());
}
?
private void write() {
//任務(wù)異步處理
executorService.submit(() -> process());
}
?
/**
* task 業(yè)務(wù)處理
*/
public void process() {
//do IO ,task,queue something
}
}
在多Reactor多線程模型中廊驼,MainReactor 主要是用來(lái)處理網(wǎng)絡(luò)IO建立連接操作据过,而SubReactor則主要復(fù)雜監(jiān)聽(tīng)I(yíng)O就緒事件,分派任務(wù)執(zhí)行妒挎。此種模型中绳锅,每個(gè)模塊的工作更加專一,耦合度更低酝掩,性能和穩(wěn)定性也大量的提升鳞芙,支持的可并發(fā)客戶端數(shù)量可達(dá)到上百萬(wàn)級(jí)別。
3. Netty
首先關(guān)于Netty的使用demo期虾,請(qǐng)參考:http://www.reibang.com/p/e58674eb4c7a
Netty的架構(gòu)類似于多Reactor多線程模型原朝,但是Netty默認(rèn)不使用Worker線程池執(zhí)行Handler,而是直接使用IO線程執(zhí)行讀寫任務(wù)镶苞。下圖是Netty的線程模型:
如圖所示喳坠,Netty中包含兩個(gè)NioEventLoopGroup,一個(gè)是boss茂蚓,另一個(gè)是worker壕鹉。boss負(fù)責(zé)監(jiān)聽(tīng)網(wǎng)絡(luò)連接,而worker負(fù)責(zé)分發(fā)讀寫事件聋涨。每一個(gè)NioEventLoopGroup都包含多個(gè)NioEventLoop晾浴,一個(gè)NioEventLoop本質(zhì)上是一個(gè)包含了一個(gè)Selector的SingleThreadPool。
事實(shí)上牛郑,boss類型的NioEventLoopGroup通常只包含一個(gè)NioEventLoop怠肋。
每個(gè)boss NioEventLoop循環(huán)執(zhí)行的任務(wù)包含3步:
- 第1步:輪詢accept事件;
- 第2步:處理io任務(wù)淹朋,即accept事件笙各,與client建立連接,生成NioSocketChannel础芍,并將NioSocketChannel注冊(cè)到某個(gè)worker NioEventLoop的selector上杈抢;
- 第3步:處理任務(wù)隊(duì)列中的任務(wù),runAllTasks仑性。任務(wù)隊(duì)列中的任務(wù)包括用戶調(diào)用eventloop.execute或schedule執(zhí)行的任務(wù)惶楼,或者其它線程提交到該eventloop的任務(wù)。
每個(gè)worker NioEventLoop循環(huán)執(zhí)行的任務(wù)包含3步:
- 第1步:輪詢r(jià)ead诊杆、write事件歼捐;
- 第2步:處理io任務(wù),即read晨汹、write事件豹储,在NioSocketChannel可讀、可寫事件發(fā)生時(shí)進(jìn)行處理淘这;
- 第3步:處理任務(wù)隊(duì)列中的任務(wù)剥扣,runAllTasks巩剖。
Client端的Netty架構(gòu)圖如下:
client端啟動(dòng)時(shí)connect到server,建立NioSocketChannel钠怯,并注冊(cè)到某個(gè)NioEventLoop的selector上佳魔。client端只包含1個(gè)NioEventLoopGroup,每個(gè)NioEventLoop循環(huán)執(zhí)行的任務(wù)包含3步:
- 第1步:輪詢connect晦炊、read鞠鲜、write事件;
- 第2步:處理io任務(wù)刽锤,即connect镊尺、read、write事件并思,在NioSocketChannel連接建立庐氮、可讀、可寫事件發(fā)生時(shí)進(jìn)行處理宋彼;
- 第3步:處理非io任務(wù)弄砍,runAllTasks。
3.1 Netty模式
下面是多Reactor的使用模式:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
bossGroup中只有一個(gè)線程(EventLoop)输涕,而workerGroup中的線程是 CPU 核心數(shù)乘以2音婶, 因此對(duì)應(yīng)的到 Reactor 線程模型中,我們知道莱坎,這樣設(shè)置的 NioEventLoopGroup 其實(shí)就是多Reactor模型衣式。
下面是單Reactor單線程的使用模式:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup).channel(NioServerSocketChannel.class)
注意, 我們實(shí)例化了一個(gè) NioEventLoopGroup,構(gòu)造器參數(shù)是1檐什,表示 NioEventLoopGroup 的線程池大小是1碴卧。然后接著我們調(diào)用 b.group(bossGroup) 設(shè)置了服務(wù)器端的 EventLoopGroup。此時(shí)bossGroup和workerGroup就是同一個(gè)NioEventLoopGroup乃正,并且這個(gè) NioEventLoopGroup只有一個(gè)線程(EventLoop)住册,那么對(duì)應(yīng)到Reactor的線程模型中,就相當(dāng)于單Reactor單線程模型瓮具。
3.2 耗時(shí)任務(wù)
由于Netty中的EventLoop既要處理IO荧飞,又要執(zhí)行Handler。因此需要使用特殊手段執(zhí)行耗時(shí)任務(wù)名党。主要有兩種方式:
- Handler中加入自定義線程池
- Pipeline中加入線程池
方法一:自定義線程池
public class ServerBusinessThreadPoolHandler extends SimpleChannelInboundHandler {
public static final ChannelHandler INSTANCE = new ServerBusinessThreadPoolHandler();
private static ExecutorService threadPool = Executors.newFixedThreadPool(1000);
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
ByteBuf data = Unpooled.directBuffer();
data.writeBytes(msg);
threadPool.submit(() -> {
try {
//耗時(shí)的操作
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = getResult(data);
ctx.channel().writeAndFlush(result);
});
}
}
方法二:Pipeline中加入線程池
ServerBootstrap bootstrap = new ServerBootstrap();
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
bootstrap.group(boss, worker);
EventLoopGroup businessGroup = new NioEventLoopGroup(1000); //大小為1000的線程池
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline p = socketChannel.pipeline();
p.addLast(businessGroup, new NettyServerHandler()); // 添加NettyServerHandler叹阔,用來(lái)處理Server端接收和處理消息的邏輯
}
});
如圖,通過(guò)ChannelPipeline.addLast(EventExecutorGroup group, ChannelHandler handler)
API為對(duì)應(yīng)的Handler提供了優(yōu)先選擇的executor传睹。如果直接ChannelPipeline.addLast(ChannelHandler handler)
方法耳幢,那么Handler執(zhí)行時(shí)默認(rèn)使用對(duì)應(yīng)的NioEventLoop來(lái)執(zhí)行。而通過(guò)ChannelPipeline.addLast(EventExecutorGroup group, ChannelHandler handler)
API Netty將使用給定的EventExecutorGroup來(lái)執(zhí)行handler蒋歌。
3.3 Netty避免線程切換
為了盡可能的提升性能帅掘,Netty在很多地方進(jìn)行了無(wú)鎖化設(shè)計(jì),例如在IO線程內(nèi)部進(jìn)行串行操作堂油,避免多線程競(jìng)爭(zhēng)導(dǎo)致的性能下降問(wèn)題修档。表面上看,串行化設(shè)計(jì)似乎CPU利用率不高府框,并發(fā)程度不夠吱窝,但是,通過(guò)調(diào)整NIO線程池的線程參數(shù)迫靖,可以同時(shí)啟動(dòng)多個(gè)串行化的線程并行運(yùn)行院峡,這種局部無(wú)鎖化的串行線程設(shè)計(jì)相比一個(gè)隊(duì)列---多個(gè)工作線程的模型性能更優(yōu)。
Netty的NioEventLoop讀取到消息之后系宜,直接調(diào)用ChannelPipeline的fireChannelRead(Object msg)照激。只要用戶不主動(dòng)切換線程,一直都是由NioEventLoop調(diào)用用戶的Handler盹牧,期間不進(jìn)行線程切換俩垃。這種串行處理方式避免了多線程操作導(dǎo)致的鎖的競(jìng)爭(zhēng),從性能角度看是最優(yōu)的汰寓。
4. tomcat
Tomcat也采用了Reactor模型的設(shè)計(jì)理念口柳,如下圖所示:
如圖所示,Tomcat線程模型中包含四個(gè)關(guān)鍵角色:
- Acceptor:負(fù)責(zé)處理Socket連接有滑。獲得SocketChannel對(duì)象跃闹,然后封裝在一個(gè)tomcat的實(shí)現(xiàn)類org.apache.tomcat.util.net.NioChannel對(duì)象中。然后將NioChannel對(duì)象封裝在一個(gè)PollerEvent對(duì)象中毛好,并將PollerEvent對(duì)象壓入Poller Event Queue里望艺。
- Poller:每一個(gè)Poller線程都維護(hù)了一個(gè)Selector對(duì)象,主要負(fù)責(zé)消費(fèi)Event Queue中的數(shù)據(jù)睛榄,并注冊(cè)到內(nèi)部的Selector上荣茫,然后不斷監(jiān)聽(tīng)Socket讀寫就緒事件。讀寫就緒后场靴,將就緒的SocketChannel傳遞給Worker線程池執(zhí)行讀寫任務(wù)啡莉。
- Poller Event Queue:存儲(chǔ)PollerEvent對(duì)象的消息隊(duì)列。注意旨剥,這個(gè)消息隊(duì)列實(shí)際上存儲(chǔ)在Poller中咧欣,Poller中包含了一個(gè)
private final SynchronizedQueue<PollerEvent> events = new SynchronizedQueue<>();
屬性用于存儲(chǔ)PollerEvent。并且Poller開(kāi)放了void addEvent(PollerEvent event)
方法轨帜,從而Acceptor能夠?qū)ollerEvent傳遞給Poller魄咕。 - Worker:實(shí)際的IO讀寫線程。
多個(gè)Worker線程蚌父,有時(shí)候也叫IO線程哮兰,就是專門負(fù)責(zé)IO讀寫的毛萌。一種實(shí)現(xiàn)方式就是像Netty一樣,每個(gè)Worker線程都有自己的Selector喝滞,可以負(fù)責(zé)多個(gè)連接的IO讀寫事件阁将,每個(gè)連接歸屬于某個(gè)線程。另一種方式實(shí)現(xiàn)方式就是有專門的線程負(fù)責(zé)IO事件監(jiān)聽(tīng)右遭,這些線程有自己的Selector做盅,一旦監(jiān)聽(tīng)到有IO讀寫事件,并不是像第一種實(shí)現(xiàn)方式那樣(自己去執(zhí)行IO操作)窘哈,而是將IO操作封裝成一個(gè)Runnable交給Worker線程池來(lái)執(zhí)行吹榴,這種情況每個(gè)連接可能會(huì)被多個(gè)線程同時(shí)操作,相比第一種并發(fā)性提高了滚婉,但是也可能引來(lái)多線程問(wèn)題图筹,在處理上要更加謹(jǐn)慎些。tomcat的NIO模型就是第二種让腹。
參考文章:
- https://blog.csdn.net/Pengjx2014/article/details/79179129#nioeventloop
- http://www.reibang.com/p/03bb8a945b37
- http://www.reibang.com/p/e58674eb4c7a
- http://www.reibang.com/p/727bbc7454dc
- https://blog.tolvyou.cn/2018/11/16/netty-asyc-callback/
- https://blog.csdn.net/qq_16681169/article/details/75003640
- https://blog.csdn.net/yanlinwang/article/details/46382889