線程使得計算機可以同時執行多個任務,并且各個任務之間相互分離,各自的獨立運行,當然資源還是由機算機來分配的,像內存,文件句柄,安全證書,如果需要的話,線程會通過一些原如的機制相互進行通信:Socket,信號處理(signal handlers),共享內存,信號量和文件等等。
而隨著計算機朝著多核的方向邁進,計算機的處理方式也由原來的單線程安照時間片的處理方式改變為多核同時處理時間片(參考:JAVA CONCURRENCY IN PRACTICE),所以更好的理解多線程有助于我們寫出效率更高的程序。
今天我想討論的是如何更好的利用JAVA的多線程來執行任務。現在大多數服務器像大家常聽說的:Web服務器,郵件服務器,文件服務器,EJB容器等,都是接受遠程客戶通過網絡連接發送的請求,然后對其做出處理,而且每個請求之間不會受其他請求的影響,并且,服務器只須用其很小的一部分資源就可以處理單一的消息。
而服務器內部的調動策略是我們今天要討論的。
第一種,代碼如下:
Class SingleThreadWebServer{
Public static void main(String [] args) throws IOException{
ServerSocket socket=new ServerSocket(80);
While(true){
Socket connection=socket.accept();
//處理connection
}
}
}
這種策略一次只能處理一個請求,效率無疑是非常差的。當系統需要在處理完connection,以后才會回到頭來調用accept().我來簡單的解釋一下為什么
1. 當socket接受到一個請求A時,如果這時再過來一個B請求,而系統正在處理當前的請求,固無法去處理B請求,此時B請求會暫時的阻塞,而B請求的阻塞而使服務器浪費的資源是不比處理A資源時小的。等到A請求處理完時。才能繼續處理
2. 在等待請求時,也就是單線程服務在等待它的I/O操作,CPU會處于閑置狀態,當然利用率也非常之低了。
所以,這種使用策略不是很推薦使用,除非你的服務器只有唯一的一個用戶,而且服務器在同一時間內只需只時處理一個請求除外。
第二種,代碼如下:
Class ThreadPerTaskWebServer{
Public static void main(String []args)throws IOException{
ServerSocket socket=new ServerSocket(80);
While(true){
Final Socket connection=socket.accept();
Runnable task=new Runnable(){
Public void runj(){
//處理connection
}
};
new Thread(task).start();
}
}
}
在第二種情況下,系統的主線也就是main的主方法不斷的交替運行“接受外部連接”,“轉發請求”,而且主循環為每個連接都創建一個新線程以處理請求,且并不在主循環內部處理它。和第一種方法相比,他有下面幾種優勢
1. 執行任務不放在主線程,主線程主要負責等待新的連接,這樣程序就可以在完成前面的請求之前在接受新的請求,響應也相應的提高了
2. 程序并行的處理任務,因為多個線程可以在完成請求之前接新請求,程序的吞吐量得到了很大的提高
3. 代碼是線程安全的,多個任務并發的調用它。
第二種是對第一種的良好的改進,也是現在大多數主流的Web服務器所用的連接和使用方式,而且只要請求的到達速度沒有超出服務器的請求能力,這種方法也可以說是比較好的解決多請求的方案
當然,不能不看到。第二種方法也有其缺點:
1. 因為每個任務都會為其創建線程,所以,當線程使用過多時,系統和虛擬機會因為創建線程而消耗大量的資源
2. 而且要考慮到,如果創建線程過來,內存有限時,而且可運行的線程多于可用的處理器數,線程將會空閑,大量空閑沒處理的線程就會占據很大的閃存,所以,當你的線程的數量已經達到CPU能夠處理的最大數時。再創建只會影響效率
3. Thread在創建線程時,會要求內存空間給出一定的堆占空間,當使用過多時。就會導致虛擬機的OutOfMemoryError。本人因為工作原因也是經常用多線程,如果暴這個異常。就不得不重啟服務器。
所以,我們可以總結出,在一定的范圍內,增加線程可以提高系統的吞吐量,一旦超出范圍,線程創建再多只會OutOfMemoryError。所以我們應該想辦法設定一個范圍來限制應用程序可以創建的線程數,然后測試其最大所能承受的范圍,從而構造良好的健康的程序。具體的明天我們繼續討論,如何利用線程池來寫出更好的多線程程序。
--------------------------------
Q9原創。