生意規模擴大
話說,老王和大明的生意越來越好,這就需要兩個人增強業務往來,由于天南地北,兩個人只能每次運輸都需要雇一個人去運貨(new 一個線程),一個月下來,兩人一算,人力成本太大了,光是雇傭人一個月就用了將近一百個人,本來生意旺盛,卻還虧損了。兩個人思來想去,想到了一個好主意,可以租個宿舍,就固定雇傭幾個人,誰閑著誰就去運輸,這樣可以大大降低人的數量,又可以充分發揮人的效率。
其實上面這種思路就是服務端每次新連接客戶端不在new一個線程,二是創建一個線程池,這樣避免線程的大量創建,下面我們來用代碼實現下以上的邏輯。
首先創建老王類,和昨天的BIO沒有什么區別:
/**
* 偽異步請求 老王
*
* @author wangmj
* @since 2018/11/19
*/
public class FakeBioClient {
public static void main(String[] args) {
int port = 8761;
Socket socket = null;
PrintWriter out = null;
BufferedReader in = null;
try {
//創建客戶端soket--老王
socket = new Socket("127.0.0.1", port);
while (true) {
//獲取輸出流,向服務器發送消息
OutputStream os = socket.getOutputStream();
out = new PrintWriter(os, true);
out.println("今天給你發送東北特產100斤,發送時間:" + new Date());
System.out.println("老王的東北特產成功送出");
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//收到服務端的響應
String resp = in.readLine();
System.out.println("收到大明的反饋=" + resp);
Thread.sleep(500);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (out != null) {
out.close();
}
try {
if (in != null) {
in.close();
}
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
接著我們創建工人管理宿舍—線程池
/**
* @author wangmj
* @since 2018/11/19
*/
public class FakeBioServerExecutePool {
private ExecutorService executor;
public FakeBioServerExecutePool() {
executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), 100, 120L, TimeUnit.SECONDS, new ArrayBlockingQueue(5000));
}
public void execute(Runnable task) {
executor.execute(task);
}
}
有一點需要提醒,創建線程池不要用Excecutors類創建,盡量用new ThreadPoolExecutor創建,因為這樣可以自己定義線程池參數,并且可以自己定義拒絕策略。
現在我們繼續創建小美類,大明的具體業務處理對象:
/**
* @author wangmj
* @since 2018/11/19
*/
public class FakeBioServerHandler implements Runnable {
private Socket socket;
public FakeBioServerHandler(Socket socket) {
this.socket = socket;
}
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
//從大明收到的特產
String messageFromDM = null;
while (true) {
messageFromDM = in.readLine();
if (messageFromDM == null) {
break;
}
System.out.println("收到老王的特產=" + messageFromDM);
out.println("老鐵,已收到特產,接收時間:" + new Date());
System.out.println("通知老王已收到特產");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (out != null) {
out.close();
}
}
}
}
最后我們創建大明類:
/**
* 大明類,負責接收老王發過來的特產
*
* @author wangmj
* @since 2018/11/19
*/
public class FakeBioServer {
public static void main(String[] args) {
int port = 8761;
Socket socket = null;
try {
//創建大明類
ServerSocket serverSocket = new ServerSocket(port);
socket = serverSocket.accept();
System.out.println("創建大明類成功,并阻塞等待老王發送的特產");
FakeBioServerExecutePool executePool = new FakeBioServerExecutePool();
executePool.execute(new FakeBioServerHandler(socket));
} catch (IOException e) {
e.printStackTrace();
}
}
}
好,這樣一條龍我們就都創建完畢,包括了客戶端,服務端及服務端的線程池,讓我們運行下查看結果,
客戶端運行結果—老王類
服務端運行結果–大明類
原理分析
首先,這次創建的客戶端及服務端還是基于BIO,也就是說讀寫還是會同步阻塞,只不過這次我們將服務端每次接收新的連接不是每次都new Thread創建線程,而是用線程池來管理線程,想以此來減少線程的創建,并且設定了一個最大任務隊列;模型圖如下(模型圖用的processon畫的,畫的不怎么好)
分析:
當任務足夠多,并且網絡較差,造成一次IO的時間過多,就會造成隊列堆積大量等待任務,由于服務端只有一個acceptor處理,新的客戶端連接就會被拒絕,那么最終會導致隊列中堆積大量的請求,同時客戶端連接失敗,服務端CPU飆升,系統崩潰,所以偽異步IO模型解決不了問題