在Java5以後,通過Executor來啓動線程比用Thread的start()更好。在新特徵中,可以很容易控制線程的啓動、執行和關閉過程,還可以很容易使用線程池的特性。我們用的最多的execute方法,
0.異步返回值的場景
我們先不用線程池,來實現一個功能。加入週末了,幾個小夥伴要一塊做飯,假設做飯需要3個步驟,分別是:
1.打掃廚房衛生,準備廚具
2.買菜
3.炒菜
現在我們怎麼分工呢,現實中一般是這樣的,一部分小夥伴去買菜,一部分小夥伴打掃準備廚具,等這兩樣都做完之後才炒菜。也就是說第1步和第2步是同時進行的。
我們使用Java代碼來實現下:
public class Cook{
public static void futureCook() throws ExecutionException, InterruptedException {
Callable<Food> first = new Callable<Food>() {
@Override
public Food call() throws Exception {
//do something
return new Food();
}
};
FutureTask<Food> task1 = new FutureTask<Food>(first);
new Thread(task1).start();//先去做第一步
Object task2 = null;
//.... 第二步做了一些事
// 第三步 需要用到第一步和第二部的結果
if (!task1.isDone()) { // 第一步是否完成
System.out.println("task1 is done");
}
Food foods = task1.get();//獲取第一步的結果
cook(foods, task2);
}
private static void cook(Food foods, Object task2) {
}
static class Food{
Object food;
}
}
我們先用一個線程去買菜,執行第一步,同時執行第二步,第二步完成之後,我們試圖獲取第一步的結果,如果第一步沒有執行完,那麼就阻塞在這裏等待,直到完成。前兩步完成之後,我們就可以愉快的做飯了。
下面我們分析下使用到的技術
1.Callable
Callable是一個接口,它只有一個方法call,調用call可以異步執行,並返回結果。
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
2.FutureTask
在不用線程池的情況下,我們使用Callable來執行任務,使用Future來獲取任務結果,FutureTask實現了RunnableFuture接口:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
具體的步驟如下:
1.創建對象實現Callable接口,實現其call方法
2.使用Callable對象創建FutureTask對象
3.執行使用FutureTask創建線程執行
4.FutureTask獲取執行結果
Callable<Object> c= new Callable<Object>() {
@Override
public Objectcall() throws Exception {
return new Object();
}
};//1
FutureTask<Object> task = new FutureTask<Object>(c);//2
new Thread(task ).start();//3
Object obj = task.get();//4
這是沒有用線程池ThreadPoolExecutor的情況下,異步返回值的最簡單實現。
下面我們看下在線程池中是如何操作的
1.ThreadPoolExecutor的submit的使用場景
上面的例子我們知道了,如果想要異步執行得到返回值,就需要使用Callable。在線程池ExecutorService接口中,我們如果異步執行得到返回值呢?
ExecutorService的接口定義:
ExecutorService{
void execute(Runnable command);
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
}
其中execute方法是繼承Executor的,只接受Runnable的參數,並且無返回值。
submit方法可以接收Callable和Runnable的參數,並且有返回值Future。具體的使用方法會在後面講
Future
Future就是對於具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果。必要時可以通過get方法獲取執行結果,該方法會阻塞直到任務返回結果。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
-
cancel方法用來取消任務。如果取消任務成功則返回true,如果取消任務失敗則返回false。參數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務,如果設置true,則表示可以取消正在執行過程中的任務。如果任務已經完成,則無論mayInterruptIfRunning爲true還是false,此方法肯定返回false.
-
isCancelled方法表示任務是否被取消成功,如果在任務正常完成前被取消成功,則返回 true。
-
isDone方法表示任務是否已經完成,若任務完成,則返回true;
-
get()方法用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回;
-
get(long timeout, TimeUnit unit)用來獲取執行結果,如果在指定時間內,還沒獲取到結果,就直接返回null。
Future是一個接口,真正完成功能的是FutureTask,它的基本用法我們上一節已經說明了,我們看下它的實現:
public class FutureTask{
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
}
FutureTask內部定義了一個Callable變量。執行異步任務的時候,還是執行run方法。
1.判斷任務的狀態,如果任務不是初始狀態(NEW),則返回
2.result = c.call();調用Callable的call方法,保存返回值
3.如果執行c.call()拋出異常,則保存異常
4. set(result);保存執行結果,更新任務狀態
實踐
public static void main(String[] args) {
submitTest();
}
private static ExecutorService executorService = null;
public static class CallRunner implements Callable<Counter>{
@Override
public Counter call() throws Exception {
Counter counter = new Counter();
for (int i=0;i<10000;i++){
counter.i++;
}
System.out.println("CallRunner 執行結束");
return counter;
}
}
public static void submitTest(){
executorService = createThreadPool();
Future<Counter> future = executorService.submit(new CallRunner());
executorService.execute(new T(2));
try {
Counter counter = future.get();
System.out.println("count = "+counter.i);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
private static ThreadPoolExecutor createThreadPool() {
int corePoolSize = 3;
int maximumPoolSize = 5;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(20);
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 5, TimeUnit.MINUTES, workQueue);
return executor;
}
public static class Counter{
int i = 0;
}
public static class T implements Runnable{
@Override
public void run() {
System.out.println("Runnable 執行完畢");
}
}
上面的例子中我們使用 Future submit(Callable task)這個接口:
Future future = executorService.submit(new CallRunner());
在看下這3個接口,
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
Future submit(Runnable task, T result)怎麼使用呢?很簡單,我們定義一個對象result,然後當做第二個參數傳入submit方法中。在call中將結果設置到result中。這樣就不用Future.get()方法獲取結果了。當然這樣用的很少,具體看使用場景了。
Future<?> submit(Runnable task)這個接口有什麼用的?我們知道Runnable 的run方法是沒有返回值的,但是仍然返回Future。雖然不要發返回值,但是我們可以用Future獲取異步任務的狀態啊,是否完成、取消;get()方法阻塞直到任務執行完
2.execute 和 submit的區別
看了上面的例子,就容易理解execute 和 submit的區別了。
1.參數不同
execute只能接收Runable參數
submit接收Runable,Callable
2.返回值不同
execute沒有返回值,
submit返回Future對象,可以查詢任務的執行狀態和執行結果
3.異常處理不同
execute中的是Runnable接口的實現,execute中出現的異常,會被直接拋出來,或者只能在run方法中捕獲。
submit後出現的異常,可以在call方法中捕獲處理,也可以在外部處理。而你又希望外面的調用者能夠感知這些exception並做出及時的處理,那麼就需要用到submit,通過捕獲Future.get拋出的異常。如果不調用Future.get方法,則即便有錯誤異常,也不會拋出。