標籤:web伺服器 平台 有用 生命週期 並發 啟動 瞭解 問題 throws
一,線上程中執行任務
1,無限建立線程的不足:
- 線程生命週期的開銷非常高。線程的建立過程需要時間,這就延遲了請求的處理,並且需要JVM和作業系統提供一些輔助操作。
- 資源消耗。如果可運行線程數量多於可用處理器的數量,那麼有些線程會閑置就會佔用許多記憶體,如果大量線程在競爭CPU還會產生其他的效能消耗。
- 穩定性。在可建立線程的數量上有一個閾值,這個閾值隨著平台不同而不同,並且受多個因素制約,包括JVM的啟動參數、Thread建構函式中請求的棧大小,以及底層作業系統對線程的限制等。如果超過這個限制,就很可能有OOM異常。
在一定的範圍內,增加線程可以提高系統的輸送量,但如果超過這個範圍,再建立更多的線程只會降低程式的執行速度,如果過多地建立線程,整個系統就有可能崩潰。要想避免這種危險,就應該對應用程式可以建立的線程數量進行限制,並且全面地測試應用程式,從而確保線程數量達到限制時,程式也不會耗盡資源。
2,Executor架構
串列執行的問題在於其糟糕的響應性和輸送量,而“為每個任務都分配一個線程”的問題在於資源管理的複雜性。所以就有了線程池,線程池簡化了線程的管理工作,並且java.util.concurrent提供了一種靈活的線程池作為Executor架構的一部分。在java類庫中,任務執行的不是Thread,而是Executor:
public interface Executor { void execute(Runnable command);}
1,基於Executor的Web伺服器
class TaskExecutorWebServer { private static final int NTHREAD = 100; private static final Executor exe = Executors.newFixedThreadPool(NTHREAD); public static void main(String[] args) { ServerSocket socket = new ServerSocket(80); while(true) { final Socket connection = socket.accept(); Runnable task = new Runnable() { public void run() { handleRequest(connection); } }; exec.execute(task); } }}
在TaskExecutionWebServer中,通過使用Executor,將請求處理任務的提交與任務的實際執行解耦開來。
也可以很容易地將上例修改為ThreadPerTaskWebServer的行為,只需要使用一個為每個請求都建立新線程的Executor。
public class ThreadPerTaskExecutor implements Executor { public void execute(Runnable r) { new Thread(r).start(); }}
還可以編寫一個Executor使ThreadPerTaskExecutor的行為類似於單線程的行為:
public class WithinThreadExecutor implements Executor { public void execute(Runnable r) { r.run(); }}
***************************************************
每當看到下面形式的代碼時,並且希望獲得一種更靈活的執行策略時,考慮使用Executor來代替Thread:
new Thread(runnable).start();
***************************************************
3, 線程池
線程池是指管理一組相同背景工作執行緒的資源集區。線程池是與工作隊列密切相關的,其中在工作隊列中儲存了所有等待執行的任務。背景工作執行緒的任務很簡單: 從工作隊列中擷取一個任務,執行任務,然後返回線程池並等待下一個任務。
“線上程池中執行任務”比“為每個任務分配一個線程”優勢更多。通過重用現有的線程而不是建立新線程,可以減少線上程建立與銷毀的開銷。另一個好處是請求到來時,不會再因為要等待線程建立而延遲,也就提高了響應性。通過適當調整線程池的大小,可以建立足夠多的線程以便處理器保持忙碌狀態,同時還可以防止過多線程互相競爭資源而使應用程式耗盡記憶體。
類庫提供了一個靈活的線程池以及一些有用的預設配置。可以通過調用Executors中的靜態Factory 方法之一來建立一個線程池:
- newFixedThreadPool:建立一個固定長度的線程池,每當提交一個任務時就建立一個線程,直到達到線程池的最大數量,這時線程池的規模將不再變化。
- newCachedThreadPool:建立一個可快取的線程池,如果線程池的當前規模超過了處理需求時,那麼將回收閒置線程,而當需求增加時,則可以添加新的線程,線程池的規模不存在任何限制。
- newSingleThreadExecutor:是一個單線程的Executor,它建立單個背景工作執行緒來執行任務,如果這個線程異常結束,會建立另一個線程來替代。它能確保依照任務在隊列中的順序來串列執行。
- newScheduledThreadPool:建立了一個固定長度的線程池,而且以延遲或定時的方式執行任務,類似於Timer。
4,Executor的生命週期
由於Executor以非同步方式來執行任務,因此在任何時刻,之前提交任務的狀態不是立即可見的。有些任務可能已經完成,有些可能正在運行,而其他的任務可能在隊列中等待執行。當關閉應用程式時,可能採用平緩的方式(完成所有已經啟動的任務,並且不再接受任何新的任務),也可能採用粗暴方式(直接所有都關掉)。Executor視為應用程式提供服務的,因此它們也是可關閉的,並把在關閉操作中受影響的任務的狀態返回給應用程式。
為瞭解決執行任務的生命週期問題,ExecutorService介面擴充了Executor,添加了一些用於生命週期管理的方法:
public interface ExecutorService extends Executor { void shutdown(); List<Runnable> shutDownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; ...}
ExectuorService的生命週期有三種狀態:
《Java並發編程實戰》(五)---- 任務執行