java線程池學習(四) —— Executors類

上一章我們介紹了ExecutorService接口,以及它的實現類ThreadPoolExecutor

那麼這裏我們將介紹Executors類,它可以更進一步的簡化我們的工作,直接創建一些預定義過的線程池

這個類也在java.util.concurrent包下。它有如下的幾個比較常用的創建線程池的方法:

一:newFixedThreadPool

創建一個線程可重用的並且線程數固定的線程池。

nThreads - 池中的線程數

threadFactory - 創建新線程時使用的工廠

public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads,ThreadFactory threadFactory)

二:newCachedThreadPool

創建一個可根據實際情況動態維持線程數的線程池,當任務到來時,如果有已經構造好的空閒線程將重用它們,不創建新的線程。

如果沒有可用的空閒線程,則創建一個新線程並添加到池中。並且會終止並從緩存中移除那些已有 60 秒鐘未被使用的線程。因

此,長時間保持空閒的線程池不會使用任何資源。

threadFactory - 創建新線程時使用的工廠

public static ExecutorService newCachedThreadPool()
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

三:newSingleThreadExecutor

創建一個使用單個線程的 ExecutorService,以無界隊列方式來運行該線程。

threadFactory - 創建新線程時使用的工廠

public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)

關於這3類的線程池我引用一下《thinking in java》對它們的描述:

使用FixedThreadPool,你可以一次性的預先執行代價高昂的線程分配,因而也就可以限制線程數的數量,這可以節省時間,因爲你不用爲每一個任務都固定的付出創建線程的開銷。在事件驅動的系統中,需要線程的事件處理器,通過直接從池中獲取線程,也可以如你所願地儘快得到服務。你不會濫用可獲得的資源,因爲FixedThreadPool使用的Thread對象的數量是有界的。

對於CachedThreadPool,它在程序的執行過程中通常會創建與所需數量相同的線程,然後在它回收舊線程時停止創建新線程,因此它是合理的ExecutorService的首選。只有當這種方式會引發問題時,你才需要切換到FixedThreadPool。

SingleThreadExecutor就是線程數量爲1的FixedThreadPool。這對於你希望在另一個線程中連續運行的任何事物(長期存活的任務)來說,都是非常有用的,例如監聽進入套接字連接的任務。

=======================================================================

我們再來看一下它們的源碼,比如FixedThreadPool:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

所以在本質上,就是創建了一個我們上一篇文章介紹過的ThreadPoolExecutor類。

=======================================================================

接下來我們寫一個完整的,使用它們的例子:

下面給出了一個網絡服務的簡單結構,這裏線程池中的線程作爲傳入的請求。它使用了預先配置的 Executors.newFixedThreadPool(int) 方法創建線程池:

class NetworkService implements Runnable {
    private final ServerSocket serverSocket;
    private final ExecutorService pool;

    public NetworkService(int port, int poolSize)
        throws IOException {
      serverSocket = new ServerSocket(port);
      pool = Executors.newFixedThreadPool(poolSize);
    }
 
    public void run() { // run the service
      try {
        for (;;) {
          pool.execute(new Handler(serverSocket.accept()));
        }
      } catch (IOException ex) {
        pool.shutdown();
      }
    }
}

class Handler implements Runnable {
    private final Socket socket;
    Handler(Socket socket) { this.socket = socket; }
    public void run() {
      // read and service request on socket
    }
}

============================

可以返回結果的“Runnable”

我們知道ExecutorService框架使用Runnble作爲其基本的任務表示形式。但是它有很大的侷限性就是它不能返回一個值,或者拋出一個受檢查的異常

實際上許多需要知道執行結果的任務都需要一定的執行時間的,比如執行數據庫的查詢,或者從網絡上獲取一些資源,更或者進行一些比較複雜的計算。對於這些任務Callable是一種更好的抽象。你可以把它當成有返回值的“Runnable”,它的call()方法就相當於Runnable的run()方法。但關鍵是它的call()方法將返回一個值,並可能拋出一個異常。

那麼怎麼在ExecutorService框架中很好的使用Callable呢。這裏就需要使用到Feture接口。

ExecutorService中的所有submit方法都將返回一個Future對象,從而可以將Callable提交給ExecutorService,並得到一個Future用來獲得任務的執行結果或者取消任務。

public class Test {
	private final ExecutorService executor = Executors.newFixedThreadPool(3);
	public void runTheTask(){
		Future<String> future = executor.submit(new Callable<String>(){

			@Override
			public String call() throws Exception {
				Thread.sleep(3000);
				return "result";
			}
			
		});
		try {
			System.out.println( future.get());
		} catch (InterruptedException | ExecutionException e) {
		}
	}
	
	public static void main(String args[]){
		Test t = new Test();
		t.runTheTask();
	}
}
運行後在future.get()步會阻塞 直到3秒後 返回結果“result”

上面的線程只執行了一個Callable任務。

但有某些情景下需要我們執行好幾個Callable任務,並且要獲得它們的返回結果,代碼就變得很不好控制了

public class Test {
	private final ExecutorService executor = Executors.newFixedThreadPool(3);
	public void runTheTask(){
		Future<String> future1 = executor.submit(new Callable<String>(){

			@Override
			public String call() throws Exception {
				Thread.sleep(3000);
				return "result1";
			}
			
		});
		Future<String> future2 = executor.submit(new Callable<String>(){

			@Override
			public String call() throws Exception {
				Thread.sleep(3000);
				return "result2";
			}
			
		});
		Future<String> future3 = executor.submit(new Callable<String>(){

			@Override
			public String call() throws Exception {
				Thread.sleep(3000);
				return "result3";
			}
			
		});
		try {
			System.out.println( future1.get());
			System.out.println( future2.get());
			System.out.println( future3.get());
		} catch (InterruptedException | ExecutionException e) {
		}
	}
	
	public static void main(String args[]){
		Test t = new Test();
		t.runTheTask();
	}
}

可以看到 上面的代碼非常難看而且不好控制。

幸運的是,在這種情況下我們可以使用CompletionService來實現
CompletionService將ExecutorService和BlockingQueue功能融合在了一起。

你可以將Callable任務提交給它來執行,它執行完返回的Future結果會放進BlockingQueue中。

你再用類似於隊列操作的take和poll方法來獲得已完成的結果。

public class Test2 {
	
	private final ExecutorService executor = Executors.newFixedThreadPool(3);
	
	public void runTasks(){
		CompletionService<String> completionService = new ExecutorCompletionService<String>(executor);
		completionService.submit(new Callable<String>(){
			@Override
			public String call() throws Exception {
				return "result1";
			}
		});
		completionService.submit(new Callable<String>(){
			@Override
			public String call() throws Exception {
				return "result2";
			}
		});
		completionService.submit(new Callable<String>(){
			@Override
			public String call() throws Exception {
				return "result3";
			}
		});
		
		for(int i=0;i<3;i++){
			try {
				Future<String> f = completionService.take();
				System.out.println(f.get());
			} catch (InterruptedException e) {
			} catch (ExecutionException e) {
			}
		}
	}

	public static void main(String[] args) {
		Test2 t = new Test2();
		t.runTasks();
	}
}

我們可以看到我們使用了一個ExecutorService作爲參數來初始化CompletionService。多個CompletionService可以共享一個ExecutorService。因此可以創建一個對於特定計算私有,又能共享一個ExecutorService的應用。

============================

這裏我們再順帶介紹一下線程池的關閉——shutdown()和shutdownNow()方法。

我們可以通過調用線程池的shutdown或shutdownNow方法來關閉線程池,但是它們的實現原理不同,shutdown的原理是隻是將線程池的狀態設置成SHUTDOWN狀態,然後中斷所有沒有正在執行任務的線程。對shutdown()方法的調用可以防止新的任務被提交給這個線程池,當前線程將繼續運行在shutdown()被調用之前提交的所有任務

shutdownNow的原理是遍歷線程池中的工作線程,然後逐個調用線程的interrupt方法來中斷線程,所以無法響應中斷的任務可能永遠無法終止。shutdownNow會首先將線程池的狀態設置成STOP,然後嘗試停止所有的正在執行或暫停任務的線程,並返回等待執行任務的列表。

只要調用了這兩個關閉方法的其中一個,isShutdown方法就會返回true。當所有的任務都已關閉後,才表示線程池關閉成功,這時調用isTerminaed方法會返回true。至於我們應該調用哪一種方法來關閉線程池,應該由提交到線程池的任務特性決定,通常調用shutdown來關閉線程池,如果任務不一定要執行完,則可以調用shutdownNow。

我們加入shutdown()和shutdownNow()方法來完善一下上面這個例子:

void shutdownAndAwaitTermination(ExecutorService pool) {
   pool.shutdown(); // 防止新的任務被提交上來
   try {
     // 等待當前已經存在的任務執行完
     if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
       pool.shutdownNow(); // 如果過了指定時間還有任務沒有完成,立馬停止它們
       // 等待任務響應取消命令
       if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
     }
   } catch (InterruptedException ie) {
     // (Re-)Cancel if current thread also interrupted
     pool.shutdownNow();
     // Preserve interrupt status
     Thread.currentThread().interrupt();
   }
 }

至此,對Executors的簡單瞭解結束。下一篇文章我們將對上一篇文章介紹的ThreadPoolExecutor類做一下更深入的研究。

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