前言Callable與Future
在介紹線程池前,我們先介紹下Callable與Future因爲等會封裝異步任務會用到.而異步任務Runnable相信都在熟悉不過了,Callable與Runnable類似,但Callable有返回值.
public interface Callable<V> {
V call() throws Exception;
}
類型參數就是返回值類型,比如Callable<Integer>
表示一個返回值類型爲Integer的Callable.
至於異步任務Callable的執行結果會通過一個Future對象返回,Future接口具體有如下方法.
public interface Future<V> {
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
}
get()方法獲取Callable執行結果,如果還未執行完將會阻塞直到可以獲得結果,get(long timeout, TimeUnit unit)調用超時會拋出TimeoutException異常,如果運行該計算的線程被中斷了,兩個方法都將拋出InterruptedException.如果執行已經完成,那麼get方法立即返回.
如果callable還在執行,isDone()方法返回false,如果完成了返回true.
可以用cancel取消計算,如果計算還沒開始,它將被取消不在開始,如果計算已經運行中,那麼mayInterruptIfRunning爲true,它就被中斷.
可以說Future裏面不僅帶着執行結果,並且如果你想中斷當前計算也可以通過它.
使用的時候我們需要用FutureTask包裝一下,FutureTask可以將Callable轉換成Future和Runnable,它同時實現了二者的接口,下面我們來看一下如何使用
public class MyClass {
public static void main(String[] args){
FutureTask<Integer> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
try {
task.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
static class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 100;
}
}
}
使用非常簡單這裏就不在過多贅言.
Executors線程池
創建一個新的線程是有一定代價的,如果程序創建了大量生命週期很短的線程,就應該使用線程池.
執行器Executors有很多靜態工廠方法可以構建線程池,具體如下
方法 | 描述 |
---|---|
newCachedThreadPool | 必要時創建新線程,空閒線程會被保留60秒 |
newFixedThreadPool | 創建一個定長線程池,空閒線程會被一直保留,可控制線程最大併發數,超出的線程會在隊列中等待 |
newSingleThreadExecutor | 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序執行 |
newScheduledThreadPool | 創建一個定長線程池,支持定時及週期性任務執行.代替Timer |
newSingleThreadScheduledeExecutor | 用於預定執行的單線程池 |
線程池
這裏我們分兩類介紹這些線程池,先看前三個.
newCachedThreadPool 方法構造的線程池,對於每個任務,如果有空閒線程可用,立即讓它執行,如果沒有空閒線程則創建新的線程.
newFixedThreadPool 方法構造一個具有固定大小的線程池,如果提交的任務數多於空閒的線程數,那麼把得不到服務的任務放置在隊列中.
newSingleThreadExecutor 是一個大小爲1的線程池,由一個線程執行提交的任務.
這三個返回實現了ExecutorService接口的ThreadPoolExecutor類的對象.
我們可以通過ExecutorService接口的submit與execute方法執行異步任務.
void execute(Runnable command);
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
<T> Future<T> submit(Callable<T> task);
execute比較簡單,這裏我們說下submit方法.
第一個submit方法返回一個
Future<?>
,可以使用這個對象調用isDone,cancel,或者isCancel.但是get方法在完成的時候只是返回null第二個submit方法在執行完成後返回一個指定的result對象.
第三個submit提交一個Callable,並且返回對應的Future.
順帶說下它們的區別
接收的參數不同
submit有返回值,而execute沒有
submit方便Exception處理,而execute處理異常比較麻煩.
當用完一個線程池的時候,調用shutdown,會使該線程池關閉,被關閉的線程池不在接受新的任務.當所有任務執行完了,線程池關閉.另一種是調用shutdownNow,該線程池取消還未開始的所有任務並試圖中斷還在運行的線程.
說了這麼多來個例子看看
public static void main(String[] args){
ExecutorService threadPool = Executors.newCachedThreadPool();//創建一個線程池
Future<Integer> future = threadPool.submit(new MyCallable(1));//提交一個任務
try {
System.out.println(future.get());//獲得執行結果
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
threadPool.shutdown();//關閉線程池
}
static class MyCallable implements Callable<Integer> {
private int i;
public MyCallable(int i) {
this.i = i;
}
@Override
public Integer call() throws Exception {
Thread.sleep(i * 1000);
return i;
}
}
下面我們總結下使用線程池該做的
調用Executors類中的靜態方法獲取線程池
調用submit提交Runnable或Callable
如果想取消一個任務,或者想得到執行的結果那麼需要保存好Future對象
當不在提交任何任務的時候,調用shutdown關閉線程池.
預定任務
接下來看另外兩個線程池,Executors的newScheduledThreadPool和newSingleThreadScheduledeExecutor兩個方法返回的實現了ScheduledExecutorService接口的對象,ScheduledExecutorService接口具有預定執行和重複執行的方法,可以預定執行Runnable或者Callable在初始延遲後運行一次,也可以週期性的執行一個Runnable.
ScheduledExecutorService常用方法如下
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
在預定時間後執行任務
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
在初始化延遲結束後,週期性的執行任務,週期執行間隔period.不受任務執行時間影響.
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
在初始化延遲結束後,週期性的執行給定任務,受任務執行時間影響,當第一次執行完後延遲delay後再執行第二次.
下面一個簡單的例子,每隔兩秒執行一次的定時任務.
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override public void run() {
//do something
}
}, 0, 2000, TimeUnit.MILLISECONDS);
執行一組任務
前面我們已經介紹瞭如何使用線程池執行單個任務了,但有的時候需要執行一組任務,控制相關任務,這裏我們介紹另外兩組api.
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
invokeAny方法提交一個Callable對象集合到線程池,並返回某個已經完成了的任務結果,無法知道究竟是哪個任務的結果.適用於比如要計算一個結果但是有很多種方式,但只要有一種方式計算出結果我們就不在需要計算了這種情況.
invokeAll方法提交一個Callable對象集合到線程池,返回一個Future對象列表,代表所有任務的結果.
到這裏線程池基礎就介紹完了,歡迎吹水.