Java NIO之Reactor模型

Reactor模式思想:分而治之+事件驅動

1)分而治之

一個連接裏完整的網絡處理過程一般分爲accept、read、decode、process、encode、send這幾步。

Reactor模式將每個步驟映射爲一個Task,服務端線程執行的最小邏輯單元不再是一次完整的網絡請求,而是Task,且採用非阻塞方式執行。

2)事件驅動

每個Task對應特定網絡事件。當Task準備就緒時,Reactor收到對應的網絡事件通知,並將Task分發給綁定了對應網絡事件的Handler執行。

3)角色

Reactor:負責響應事件,將事件分發給綁定了該事件的Handler處理;

Handler:事件處理器,綁定了某類事件,負責執行對應事件的Task對事件進行處理;

Acceptor:Handler的一種,綁定了connect事件。當客戶端發起connect請求時,Reactor會將accept事件分發給Acceptor處理。

單Reactor單線程模型

優點:

  • 不需要做併發控制,代碼實現簡單清晰。

缺點:

  • 不能利用多核CPU;
  • 一個線程需要執行處理所有的accept、read、decode、process、encode、send事件,處理成百上千的鏈路時性能上無法支撐;
  • 一旦reactor線程意外跑飛或者進入死循環,會導致整個系統通信模塊不可用。

程序示例

package com.reactor;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * Reactor線程
 * 
 * @author yaosht
 *
 */
public class ServerReactorThread implements Runnable {

	// 選擇器
	public final Selector selector;
	// ServerSocketChannel
	public final ServerSocketChannel serverSocketChannel;

	/**
	 * 構造方法
	 * 
	 * @param port
	 * @throws IOException
	 */
	public ServerReactorThread(int port) throws IOException {
		// 打開一個選擇器selector
		selector = Selector.open();
		// 通過ServerSocketChannel 的open()方法創建一個ServerSocketChannel對象
		serverSocketChannel = ServerSocketChannel.open();

		InetSocketAddress inetSocketAddress = new InetSocketAddress(port);
		// 端口地址
		serverSocketChannel.socket().bind(inetSocketAddress);

		// 配置ServerSocketChannel爲非阻塞模式
		serverSocketChannel.configureBlocking(false);
		// 將該channel註冊到selector上並設置關注OP_ACCEPT監聽事件
		SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

		// 利用selectionKey的attache功能綁定Acceptor 如果有事情,觸發Acceptor
		selectionKey.attach(new Acceptor());
		System.out.println("等待客戶端的消息……");
	}

	@Override
	public void run() {
		try {
			while (true) {
				// 進行選擇操作
				while (selector.select() > 0) {

					Set<SelectionKey> keys = selector.selectedKeys();
					Iterator<SelectionKey> iterator = keys.iterator();
					System.out.println("有關注的事件發生:" + keys.size());
					while (iterator.hasNext()) {

						SelectionKey selectionKey = iterator.next();
						// 派發事件處理
						dispatch(selectionKey);
						// 刪除已選擇集合中的鍵
						iterator.remove();

					}

				} // while
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	/**
	 * 進行SelectionKey事件派發<br/>
	 * 獲取attach附加的對象並進行方法調用:Acceptor或SocketHandler<br/>
	 * 
	 * @param key
	 */
	void dispatch(SelectionKey key) {
		if (key.isValid()) {
			if (key.isAcceptable()) {
				System.out.println("Acceptor");
				Acceptor acceptor = (Acceptor) (key.attachment());
				acceptor.handle(key);
			} else if (key.isReadable()) {
				System.out.println("SocketHandler");
				SocketHandler socketHandler = (SocketHandler) (key.attachment());
				socketHandler.handle(key);

			}
		}
	}

	/**
	 * 內部類Acceptor
	 * 
	 * @author yaosht
	 *
	 */
	class Acceptor {
		public void handle(SelectionKey key) {
			try {
				ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
				// 接收該channel上的socket連接
				SocketChannel socketChannel = serverSocketChannel.accept();
				if (socketChannel != null) {
					// 將該channel設置爲非阻塞模式
					socketChannel.configureBlocking(false);
					// 將該channel註冊到selector上並設置關注OP_READ事件
					SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
					// 調用SocketReadHandlerThread來處理channel的相關事件
					selectionKey.attach(new SocketHandler());
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
package com.reactor;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

/**
 * socket讀取數據處理線程<br/>
 * 在這裏將讀取、處理、發送放到一起了<br/>
 * 
 * @author yaosht
 *
 */
public class SocketHandler {
	/**
	 * 處理讀取數據
	 */
	public void handle(SelectionKey key) {
		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
		try {
			// byteBuffer.clear();
			SocketChannel socketChannel = (SocketChannel) key.channel();
			socketChannel.read(byteBuffer);
			// 從緩衝區中get數據並進行解析計算
			byteBuffer.flip();
			byte[] bytes = new byte[byteBuffer.remaining()];
			byteBuffer.get(bytes);
			String formulaStr = new String(bytes);
			System.out.println("服務器端線程:" + Thread.currentThread().getName() + "處理客戶端("
					+ socketChannel.getRemoteAddress().toString() + ")發送的公式:" + formulaStr);
			if (formulaStr.equals("end")) {
				System.out.println("客戶端(" + socketChannel.getRemoteAddress().toString() + ")結束通信");
				socketChannel.close();
				key.cancel();
			} else if (!formulaStr.toString().contains("+")) {
				byteBuffer.clear();
				byteBuffer.put(("客戶端(" + socketChannel.getRemoteAddress().toString() + ")發送不正確的加法公式").getBytes());
				byteBuffer.flip();
				socketChannel.write(byteBuffer);
			} else {
				// 進行計算,並將結果放入到緩衝區中
				String stra = formulaStr.substring(0, formulaStr.indexOf("+"));
				String strb = formulaStr.substring(formulaStr.indexOf("+") + 1, formulaStr.length());
				int a = Integer.parseInt(stra);
				int b = Integer.parseInt(strb);
				int sum = a + b;
				byteBuffer.clear();
				System.out.println(
						"客戶端(" + socketChannel.getRemoteAddress().toString() + ")公式計算結果:" + formulaStr + "=" + sum);
				byteBuffer.put(String.valueOf(formulaStr + "=" + sum).getBytes());
				// 從緩衝區中讀取數據到通道發向客戶端
				byteBuffer.flip();
				socketChannel.write(byteBuffer);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
package com.reactor;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Random;

/**
 * 客戶端線程
 * 
 * @author yaosht
 *
 */
public class ClientThread implements Runnable {
	InetSocketAddress addr = null;

	public ClientThread(InetSocketAddress addr) {
		this.addr = addr;
	}

	@Override
	public void run() {
		String str;
		// 創建一個對象
		Random df = new Random();

		try {
			// 通過SocketChannel的靜態方法open()創建一個SocketChannel對象
			SocketChannel socketChannel = SocketChannel.open();
			// 配置該socket爲非阻塞模式
			socketChannel.configureBlocking(false);
			Selector selector = Selector.open();
			// SocketChannel連接服務器端
			// 因爲前面設置的是非阻塞,所以調用這個方法會立即返回.
			// 因此如果返回true就不需要放到select中
			if (!socketChannel.connect(addr)) {
				// 向選擇器selector註冊該socketchannel並關注OP_CONNECT事件
				socketChannel.register(selector, SelectionKey.OP_CONNECT);
				// 進行選擇
				selector.select();
				// 得到關注的事件集合
				Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
				while (iterator.hasNext()) {
					SelectionKey selectionKey = iterator.next();
					iterator.remove();
					if (selectionKey.isValid() && selectionKey.isConnectable()) {
						SocketChannel channel = (SocketChannel) selectionKey.channel();
						// 如果連接成功則完成連接
						if (channel.isConnectionPending()) {
							channel.finishConnect();
						}
						System.out.println(Thread.currentThread().getName() + "客戶端連接成功");
						// 設置該socketChannel爲非阻塞模式
						channel.configureBlocking(false);
						// 向選擇器selector註冊該socketchannel並關注OP_READ事件
						channel.register(selector, SelectionKey.OP_READ);
					}
				}
			}

			int formulaNum = 2;
			for (int j = 1; j <= formulaNum; j++) {
				// 產生兩個加法隨機數
				int num1 = df.nextInt(101);
				int num2 = df.nextInt(101);
				// 創建數據緩存區對象
				ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

				if (j == formulaNum) {
					str = "end";
					System.out.println(Thread.currentThread().getName() + "發送結束信號:" + str);
					// 將從終端讀取到的數據放到緩衝區
					byteBuffer.clear();
					byteBuffer.put(str.getBytes());
					// 讀取緩衝區中的數據到通道中,發向服務端
					byteBuffer.flip();
					socketChannel.write(byteBuffer);
				} else {
					// 拼接兩個隨機數產生加法公式
					str = num1 + "+" + num2;
					System.out.println(Thread.currentThread().getName() + "發送加法公式:" + str);
					// 將從終端讀取到的數據放到緩衝區
					byteBuffer.clear();
					byteBuffer.put(str.getBytes());
					// 讀取緩衝區中的數據到通道中,發向服務端
					byteBuffer.flip();
					socketChannel.write(byteBuffer);

					System.out.println("等待服務器端消息……");
					// 監聽讀事件,沒有則阻塞
					selector.select();

					// 清除緩衝區
					byteBuffer.clear();
					// 讀取通道中從服務器端來的數據到緩衝區中
					socketChannel.read(byteBuffer);

					byteBuffer.flip();
					// 通過緩衝區有效元素大小確定接收數組大小
					byte[] bytes = new byte[byteBuffer.remaining()];
					// 從緩衝區讀取數據
					byteBuffer.get(bytes);
					String stringBuffer = new String(bytes);
					System.out.println(Thread.currentThread().getName() + "服務器端計算結果:" + stringBuffer);
				}
			}
			socketChannel.close();
			selector.close();
		} catch (Exception e) {
			System.out.println("異常:" + e);
		}

	}
}
package com.reactor;

import java.io.IOException;

/**
 * 啓動服務器端線程
 * 
 * @author yaosht
 *
 */
public class StartServerReactor {

	public static void main(String[] args) throws IOException {
		new Thread(new ServerReactorThread(8080), "ServerReactorThread").start();
	}
}
package com.reactor;

import java.io.IOException;
import java.net.InetSocketAddress;

/**
 * 客戶端多線程啓動
 * 
 * @author yaosht
 *
 */
public class StartClient {

	public static void main(String[] args) throws IOException {
		ClientThread clientThread = new ClientThread(new InetSocketAddress("127.0.0.1", 8080));
		for (int i = 0; i < 10; i++) {
			new Thread(clientThread, "Thread-" + i).start();
		}
	}
}

單Reactor多線程

特點:

  • 有專門一個reactor線程用於監聽服務端ServerSocketChannel,接收客戶端的TCP連接請求;
  • 網絡IO的讀/寫操作等由一個worker reactor線程池負責,由線程池中的NIO線程負責監聽SocketChannel事件,進行消息的讀取、解碼、編碼和發送。
  • 一個NIO線程可以同時處理N條鏈路,但是一個鏈路只註冊在一個NIO線程上處理,防止發生併發操作問題。

程序示例

package com.reactor;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * Reactor線程
 * 
 * @author yaosht
 *
 */
public class ServerReactorThread implements Runnable {

	// 選擇器
	public final Selector selector;
	// ServerSocketChannel
	public final ServerSocketChannel serverSocketChannel;

	/**
	 * 構造方法
	 * 
	 * @param port
	 * @throws IOException
	 */
	public ServerReactorThread(int port) throws IOException {
		// 打開一個選擇器selector
		selector = Selector.open();
		// 通過ServerSocketChannel 的open()方法創建一個ServerSocketChannel對象
		serverSocketChannel = ServerSocketChannel.open();

		InetSocketAddress inetSocketAddress = new InetSocketAddress(port);
		// 端口地址
		serverSocketChannel.socket().bind(inetSocketAddress);

		// 配置ServerSocketChannel爲非阻塞模式
		serverSocketChannel.configureBlocking(false);
		// 將該channel註冊到selector上並設置關注OP_ACCEPT監聽事件
		SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

		// 利用selectionKey的attache功能綁定Acceptor 如果有事情,觸發Acceptor
		selectionKey.attach(new Acceptor());
		System.out.println("等待客戶端的消息……");
	}

	@Override
	public void run() {
		try {
			while (true) {
				// 進行選擇操作
				while (selector.select() > 0) {
					Set<SelectionKey> keys = selector.selectedKeys();
					Iterator<SelectionKey> iterator = keys.iterator();
					System.out.println("有關注的事件發生:" + keys.size());
					while (iterator.hasNext()) {
						SelectionKey selectionKey = iterator.next();
						// 派發事件處理
						dispatch(selectionKey);
					}
					// 清除該集合中的所有元素
					keys.clear();

				} // while
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	/**
	 * 進行SelectionKey事件派發<br/>
	 * 獲取attach附加的對象並進行方法調用:Acceptor或SocketHandler<br/>
	 * 
	 * @param key
	 */
	void dispatch(SelectionKey key) {
		if (key.isValid()) {
			Runnable runnable = (Runnable) key.attachment();
			// System.out.println(runnable.i);
			if (runnable != null) {
				runnable.run();
			}
		}
	}

	/**
	 * 內部類Acceptor
	 * 
	 * @author yaosht
	 *
	 */
	class Acceptor implements Runnable {
		@Override
		public void run() {
			try {
				// 接收該channel上的socket連接
				SocketChannel socketChannel = serverSocketChannel.accept();
				if (socketChannel != null) {
					// 爲每一個連接上的socketChannel創建一個SocketHandler處理類
					new SocketHandler(socketChannel, selector);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
package com.reactor;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * socket讀取數據處理線程<br/>
 * 在這裏將讀取、處理、發送放到一起了<br/>
 * 
 * @author yaosht
 *
 */
public class SocketHandler implements Runnable {
	// SocketChannel
	private SocketChannel socketChannel;
	// SelectionKey
	private SelectionKey selectionKey;

	// 這裏使用了狀態碼來防止多線程出現數據不一致等問題
	// 該SocketChannel的事件在處理中
	static final int PROCESSING = 1;
	// 該SocketChannel的事件還在排隊中
	static final int WAITING = 2;
	// volatile狀態變量
	private volatile int state = WAITING;

	// 靜態線程池
	public static final ExecutorService pool = Executors.newFixedThreadPool(16);

	public SocketHandler(SocketChannel socketChannel, Selector selector) throws IOException {

		this.socketChannel = socketChannel;

		// 將該channel設置爲非阻塞模式
		socketChannel.configureBlocking(false);

		// 將該channel註冊到selector上並設置關注OP_READ事件
		this.selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
		// attach(this)將自身對象綁定到key上,作用是使dispatch()函數正確使用
		selectionKey.attach(this);
		// Selector.wakeup()方法會使阻塞中的Selector.select()方法立刻返回
		selector.wakeup();
	}

	@Override
	public void run() {
		if (state == WAITING) {
			// 如果此時沒有線程在處理該通道的本次讀取,就提交申請到線程池進行讀寫操作
			pool.execute(new Process(selectionKey));
		} else {
			// 如果此時有線程正在進行讀寫操作,就直接return,選擇器會進行下一次選擇和任務分派
			System.out.println(this.socketChannel.toString() + "已經被處理中");
			return;
		}

	}

	/**
	 * 這是一個同步方法,因爲在reactor中的選擇器有可能會出現一種狀況:
	 * 當process線程已經要對某通道進行讀寫的時候,有可能Selector會再次選擇該通道
	 * 因爲此時該process線程還並沒有真正的進行讀寫,會導致另一線程重新創建一個process
	 * 並試圖進行讀寫操作,此時就會出現cpu資源浪費的情況,或者出現異常,因爲線程1在讀取通道內容的時候
	 * 線程2就會被阻塞,而等到線程2執行操作的時候,線程1已經對通道完成了讀寫操做 因此可以通過設置對象狀態碼來防止出現這些問題
	 */
	private synchronized void handle(SelectionKey key) {
		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
		try {
			byteBuffer.clear();
			socketChannel.read(byteBuffer);
			// 從緩衝區中get數據並進行解析計算
			byteBuffer.flip();
			byte[] bytes = new byte[byteBuffer.remaining()];
			byteBuffer.get(bytes);
			String formulaStr = new String(bytes);
			System.out.println("服務器端線程:" + Thread.currentThread().getName() + "處理客戶端("
					+ socketChannel.getRemoteAddress().toString() + ")發送的公式:" + formulaStr);
			if (formulaStr.equals("end")) {
				System.out.println("客戶端(" + socketChannel.getRemoteAddress().toString() + ")結束通信");
				socketChannel.close();
				key.cancel();
			} else if (!formulaStr.toString().contains("+")) {
				byteBuffer.clear();
				byteBuffer.put(("客戶端(" + socketChannel.getRemoteAddress().toString() + ")發送不正確的加法公式").getBytes());
				byteBuffer.flip();
				socketChannel.write(byteBuffer);
			} else {
				// 進行計算,並將結果放入到緩衝區中
				String stra = formulaStr.substring(0, formulaStr.indexOf("+"));
				String strb = formulaStr.substring(formulaStr.indexOf("+") + 1, formulaStr.length());
				int a = Integer.parseInt(stra);
				int b = Integer.parseInt(strb);
				int sum = a + b;
				byteBuffer.clear();
				System.out.println(
						"客戶端(" + socketChannel.getRemoteAddress().toString() + ")公式計算結果:" + formulaStr + "=" + sum);
				byteBuffer.put(String.valueOf(formulaStr + "=" + sum).getBytes());
				// 從緩衝區中讀取數據到通道發向客戶端
				byteBuffer.flip();
				socketChannel.write(byteBuffer);
			}
			state = WAITING;
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Handler中處理從通道讀取數據到緩衝區並進行加法計算的內部線程處理類
	 * 
	 * @author yaosht
	 *
	 */
	class Process implements Runnable {

		private SelectionKey selectionKey;

		public Process(SelectionKey selectionKey) {
			this.selectionKey = selectionKey;
			state = PROCESSING;
		}

		@Override
		public void run() {
			handle(selectionKey);
		}
	}

}
package com.reactor;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Random;

/**
 * 客戶端線程
 * 
 * @author yaosht
 *
 */
public class ClientThread implements Runnable {
	InetSocketAddress addr = null;

	public ClientThread(InetSocketAddress addr) {
		this.addr = addr;
	}

	@Override
	public void run() {
		String str;
		// 創建一個對象
		Random df = new Random();

		try {
			// 通過SocketChannel的靜態方法open()創建一個SocketChannel對象
			SocketChannel socketChannel = SocketChannel.open();
			// 配置該socket爲非阻塞模式
			socketChannel.configureBlocking(false);
			Selector selector = Selector.open();
			// SocketChannel連接服務器端
			// 因爲前面設置的是非阻塞,所以調用這個方法會立即返回.
			// 因此如果返回true就不需要放到select中
			if (!socketChannel.connect(addr)) {
				// 向選擇器selector註冊該socketchannel並關注OP_CONNECT事件
				socketChannel.register(selector, SelectionKey.OP_CONNECT);
				// 進行選擇
				selector.select();
				// 得到關注的事件集合
				Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
				while (iterator.hasNext()) {
					SelectionKey selectionKey = iterator.next();
					iterator.remove();
					if (selectionKey.isValid() && selectionKey.isConnectable()) {
						SocketChannel channel = (SocketChannel) selectionKey.channel();
						// 如果連接成功則完成連接
						if (channel.isConnectionPending()) {
							channel.finishConnect();
						}
						System.out.println(Thread.currentThread().getName() + "客戶端連接成功");
						// 設置該socketChannel爲非阻塞模式
						channel.configureBlocking(false);
						// 向選擇器selector註冊該socketchannel並關注OP_READ事件
						channel.register(selector, SelectionKey.OP_READ);
					}
				}
			}

			int formulaNum = 2;
			for (int j = 1; j <= formulaNum; j++) {
				// 產生兩個加法隨機數
				int num1 = df.nextInt(101);
				int num2 = df.nextInt(101);
				// 創建數據緩存區對象
				ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

				if (j == formulaNum) {
					str = "end";
					System.out.println(Thread.currentThread().getName() + "發送結束信號:" + str);
					// 將從終端讀取到的數據放到緩衝區
					byteBuffer.clear();
					byteBuffer.put(str.getBytes());
					// 讀取緩衝區中的數據到通道中,發向服務端
					byteBuffer.flip();
					socketChannel.write(byteBuffer);
				} else {
					// 拼接兩個隨機數產生加法公式
					str = num1 + "+" + num2;
					System.out.println(Thread.currentThread().getName() + "發送加法公式:" + str);
					// 將從終端讀取到的數據放到緩衝區
					byteBuffer.clear();
					byteBuffer.put(str.getBytes());
					// 讀取緩衝區中的數據到通道中,發向服務端
					byteBuffer.flip();
					socketChannel.write(byteBuffer);

					System.out.println("等待服務器端消息……");
					// 監聽讀事件,沒有則阻塞
					selector.select();

					// 清除緩衝區
					byteBuffer.clear();
					// 讀取通道中從服務器端來的數據到緩衝區中
					socketChannel.read(byteBuffer);

					byteBuffer.flip();
					// 通過緩衝區有效元素大小確定接收數組大小
					byte[] bytes = new byte[byteBuffer.remaining()];
					// 從緩衝區讀取數據
					byteBuffer.get(bytes);
					String stringBuffer = new String(bytes);
					System.out.println(Thread.currentThread().getName() + "服務器端計算結果:" + stringBuffer);
				}
			}
			socketChannel.close();
			selector.close();
		} catch (Exception e) {
			System.out.println("異常:" + e);
		}

	}
}

 客戶端啓動與服務器端啓動代碼一樣,就不貼了

主從Reactor多線程

在絕大多數場景下,Reactor多線程模型都可以滿足性能需求;但是在極個別特殊場景中,一個NIO線程負責監聽和處理所有的客戶端連接可能會存在性能問題。

特點:

  • 服務端用於接收客戶端連接的不再是個1個單獨的reactor線程,而是一個boss reactor線程池;
  • 服務端啓用多個ServerSocketChannel監聽不同端口時,每個ServerSocketChannel的監聽工作可以由線程池中的一個NIO線程完成。

參考netty學習系列二:NIO Reactor模型 & Netty線程模型

Java NIO之Reactor模式

JavaNIO--4.多線程Reactor模式

 

發佈了249 篇原創文章 · 獲贊 139 · 訪問量 138萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章