標籤:理解 stack err read row this 情境 worker 技術
情境: 線程池在面試時候經常會碰到,在工作中用的情境更多,所以很有必要弄清楚。
1 簡介
Java自1.5以來加入了處理一批線程的方法,也就是java並發包裡的Executor。本文主要介紹ExecutorService的用法,Runable和Callable的用法以及ExecutorCompletionService的用法。
使用Executor來執行多個線程的好處是用來避免線程的建立和銷毀的開銷,以提升效率。
因此如果某些情境需要反覆建立線程去處理同類事務的話,可以考慮使用線程池來處理。
其實Executor本身並不實現純種池的功能,只是提供了擷取ExecutorService的方法,而ExecutorService才是真正處理線程池相關邏輯的類。
Executor下擷取ExecutorService 的方法有很多,用於擷取各種不同的純種池,如單線程線程池、固定線程數的線程池等,不過最終還是調用ExecutorService的建構函式來建立,如下:
public ThreadPoolExecutor(int corePoolSize,//最少線程數 int maximumPoolSize,//最大線程數 long keepAliveTime,//線程池滿後,後續線程的等待時間 TimeUnit unit,//等待時間的單位 BlockingQueue<Runnable> workQueue,//等待線程隊列 ThreadFactory threadFactory)//線程生產工廠
1.1 Runable
通過以上方法就可以建立一個線程池方法,可以限制線程的數量和等待隊列中線程的等待時間等。然後如果要通過這個線程池來執行線程:
executorService.execute(new Runnable() { @Override public void run() { System.out.println("Execute in pool:" + Thread.currentThread().getId()); }});
通過execute()方法的執行是非同步,無法知道線程什麼時候執行完畢。
如果要想知道線程是否執行完畢,可以通過另外一個方法submit()來執行,然後擷取到一個future對象, 然後通過get()方法來判斷是否執行完畢:
Future<?> future = executorService.submit(new Runnable() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Execute in pool:" + Thread.currentThread().getId()); }});try { if(future.get()==null){ System.out.println("finish!!!"); }} catch (InterruptedException e) { e.printStackTrace();} catch (ExecutionException e) { e.printStackTrace();}
但是通過這種方式只能知道線程是否執行完畢,卻做不到將各線程的處理結果返回做歸併處理。
要實現這個目的可以使用Callable介面來封裝任務邏輯,Callable和Runable的 唯一區別就是它支援返回處理結果:
1.2 Callable
可以處理線程執行完之後的返回結果。
Future<?> future = executorService.submit(new Callable<String>() { @Override public String call() throws Exception { return "hello callable!"; }});try { System.out.println(future.get());} catch (InterruptedException e) { e.printStackTrace();} catch (ExecutionException e) { e.printStackTrace();}
其中call()方法中返回的值,就是Future對象get()到的值。但是如果有多個線程在處理,然後要將這些線程的處理結果歸併怎麼做呢?
當然可以使用ExecutorService來擷取每個放到線程池的線程的Future對象,然後遍曆的去get()然後去做歸併處理。但是顯然這種方法並不能做到先完成的就被先歸併,而是取決於遍曆到的時間,這顯然降低了處理效率。
要處理這種情境,可以使用另外一個Service–ExecutorCompletionService:
1.3 ExecutorCompletionService
package test03;import java.util.concurrent.Callable;import java.util.concurrent.CompletionService;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorCompletionService;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import org.junit.Test;public class ExecutorCompletionServiceTest { @Test public void test() { ExecutorService executorService = Executors.newFixedThreadPool(4); CompletionService<Long> completionService = new ExecutorCompletionService<Long>(executorService); for (int i = 0; i < 4; i++) { long sleep = (5 - i) * 1000; completionService.submit(new ExeWorker(sleep)); //使用CompletionService來調用線程,可以做到先完成先處理 } for(int i=0;i<4;i++){ try { System.out.println(completionService.take().get()+" Get!"); //擷取線程處理後的返回結果 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }}class ExeWorker implements Callable<Long> { private long sleep; public ExeWorker(long sleep) { this.sleep = sleep; } @Override public Long call() throws Exception { System.out.println(sleep + " Executing!"); Thread.sleep(sleep); System.out.println(sleep + " Done!"); return sleep; }}
以線程的sleep時間為線程名稱,然後輸出結果為:
可以看出後面那個迴圈擷取處理結果的地方的確是按先完成先返回的方式來實現。這種方法的一個約束就是需要知道有多少個線程在處理。(不是很理解)
其實CompletionService底層是通過一個BlockingQueue來存放處理結果,你也可以使用它自身封裝好的帶逾時的poll方法來擷取返回結果。
(轉)java並發之Executor