1. 程式人生 > >任務執行(一)

任務執行(一)

1.序列的WEB伺服器

class SingleThreadWebServer {
	public static void main(String[] args) throws IOException {
		ServerSocket socket = new ServerSocket(80);
		while(true) {
			Socket connection = socket.accept();
			handleRequest(connection);
		}
	}
}
2.為任務建立執行緒
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 run() {
					handleRequest(connection);
				}
			};
			new Thread(task).start();
		}
	}
}

    無限制建立執行緒的不足
  1. 執行緒生命週期的開銷非常高。執行緒的建立與銷燬並不是沒有代價的。根據平臺的不同,實際的開銷也有所不同。
    如果請求的到達率非常高且請求的處理過程是輕量級的,例如大多數伺服器應用程式就是這種情況那麼為每個
    請求建立一個新執行緒將消耗大量的計算資源。
  2. 資源消耗。活躍的執行緒會消耗系統資源,尤其是記憶體。如果可執行的執行緒數量多於可用處理器的數量,那麼有
    些執行緒將閒置大量空閒的執行緒會佔用許多記憶體,給垃圾回收器帶來壓力,而且大量執行緒在競爭CPU資源時還將
    產生其它的效能開銷。如果你已經擁有足夠多的執行緒使所有CPU保持忙碌狀態,那麼再建立更多的執行緒反而
    會降低效能。
  3. 穩定性。在可建立執行緒的數量上存在一個限制。這個限制值將隨著平臺的不同而不同,並且受多個因素制約,
    包括JVM的啟動引數、Thread建構函式中請求的棧大小,以及低層作業系統對執行緒的限制等。
  4. “為每個任務分配一個執行緒”這種方法的問題在於,它沒有限制可建立執行緒的數量。在32位的機器上,其中一個
    主要的限制因素是執行緒棧的地址空間。每個執行緒都要維護兩個執行棧,一個用於JAVA程式碼,另一個用於原生
    程式碼。通常,JMV在預設情況下會生成一個複合的棧,大小約為0.5MB。(可以通過JVM標誌-Xss或者通過
    Thread的建構函式來修改這個值)。如果將2的32次方除以每個執行緒的棧大小,那麼執行緒數量將被限制為幾
    千到幾萬。
    Executor框架     在JAVA類庫中,任務執行的主要抽象不是THREAD,而是Executor。
public interface Executor {
        void execute(Runnale command);
}
    Executor基於生產者-消費者模式,提交任務的操作相當於生產者,執行任務的執行緒則相當於消費者。如果要在程式中 實現一個生產者-消費者的設計,那麼最簡單的方式通常就是使用Executor。     基於Executor的web伺服器
class TaskExecutionWebServer {
	private static final int NTHREADS = 100;
	private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);
	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 run() {
					//handleRequest(connection);
				}
			};
			exec.execute(task);
		}
	}
}
    在TaskExecutionWebServer 中,通過使用Executor,將請求處理任務的提交與任務的實際執行解耦開來,並且只需採用另一 種不同的Executor實現,就可以改變伺服器的行為。     通過將任務的提交與執行解耦開來,從而無須太大的困難就可以為某種型別的任務指定和修改執行策略。各種執行策略都是一種 資源管理工具,最佳策略取決於可用的計算資源以及對服務質量的需求。     每當看到下面這種形式的程式碼時:         new Thread(runnable).start()     並且你希望獲得一種更靈活的執行策略時,請考慮使用Executor來代替Thread。

    執行緒池     類庫提供了一個靈活的執行緒池以及一些有用的預設配置可以通過呼叫Executors中的靜態工廠方法之一來建立一個執行緒池:
  1. newFixedThreadPool
  2. newCachedThreadPool
  3. newSingleThreadExecutor
  4. newScheduledThreadPool
    從“為每個任務分配一個執行緒”策略變成基於執行緒池的策略,將對應用程式的穩定性產生重大的影響:WEB伺服器不會再在高負載情況下 失敗(儘管伺服器不會因為建立了過多 的執行緒而失敗,但在足夠長的時間內,如果途程到達的速度總是超過任務執行的速度,那麼伺服器 仍有可能耗盡記憶體,因為等待執行的Runnable佇列將不斷增長。可以通過使用一個有界佇列在Executor框架內部解決這個問題)。由於 伺服器不會建立數千個執行緒來爭奪有限的CPU和記憶體資源,因此伺服器的效能將平緩的降低。通過使用Executor,可以實現各種調優、 管理、監視、記錄日誌、錯誤報告和其它功能。     為了解決執行服務的生命週期問題,ExecutorService擴充套件了Exceutor介面,添加了一些用於生命週期管理的方法。
public interface ExecutorService extends Executor {
    void  shutdown();
    List<Runnable> shutdownNow();
    boolean isShutDown();
    boolean isTerminated();
    boolean awaitTermination(long timeout,TimeUnit unit) throws InterruptedException;
}
    支援關閉操作的WEB伺服器
class LifecycleWebServer {
	private final ExecutorService exec = ...;
	public void start() throws IOException {
		ServerSocket socket = new ServerSocket(80);
		while(!exec.isShutdown()) {
			try{
				final Socket conn = socket.accept();
				exec.execute(new Runnable(){
					public void run() {
						//handleRequest(conn);
					}
				});
			} catch(RejectedExecutionException e) {
				if(!exec.isShutdown()) {
					//log("task submission rejected",e);
				}
			}
		}
	}
	
	public void stop() {
		exec.shutdown();
	}
	
	void handleRequest(Socket connection) {
		Request req = readRequest(connection);
		if(isShutdownRequest(req)) {
			stop();
		} else {
			dispatchRequest(req);
		}
	}
}