目錄
一、Runnable
與Callable
1.Runnable
與Callable
對比
通常實現一個線程我們會使用繼承Thread的方式或者實現Runnable接口,這兩種方式有一個共同的缺陷就是在執行完任務之後無法獲取執行結果。從Java1.5之後就提供了Callable與Future,這兩個接口就可以實現獲取任務執行結果。
Callable
接口`:public interface Callable<V> { V call() throws Exception; }
Runnable
接口public interface Runnable { public abstract void run(); }
- 都是接口;
- 都可以編寫多線程程序;
- 都採用
Thread.start()
啓動線程;
Runnable
沒有返回值;Callable
可以返回執行結果,是個泛型,和Future
、FutureTask
配合可以用來獲取異步執行的結果Callable
接口的call()
方法允許拋出異常;Runnable
的run()
方法異常只能在內部消化,不能往上繼續拋
注:Callalbe
接口支持返回執行結果,需要調用FutureTask.get()
得到,此方法會阻塞主進程的繼續往下執行,如果不調用不會阻塞;
二、Future
接口
1.Future
接口
Future
接口提供方法來檢測任務是否被執行完,等待任務執行完獲得結果,也可以設置任務執行的超時時間。這個設置超時的方法就是實現Java程序執行超時的關鍵。Future
接口是一個泛型接口,嚴格的格式應該是Future<V>
,其中V
代表了Future
執行的任務返回值的類型。
2.Future
接口的方法
boolean cancel (boolean mayInterruptIfRunning)
取消任務的執行。參數指定是否立即中斷任務執行,或者等等任務結束boolean isCancelled ()
任務是否已經取消,任務正常完成前將其取消,則返回true
boolean isDone ()
任務是否已經完成。需要注意的是如果任務正常終止、異常或取消,都將返回true
V get () throws InterruptedException
,ExecutionException
等待任務執行結束,然後獲得V
類型的結果。InterruptedException
線程被中斷異常,ExecutionException
任務執行異常,如果任務被取消,還會拋出CancellationException
V get (long timeout, TimeUnit unit) throws InterruptedException, ExecutionException
,TimeoutException
同上面的get
功能一樣,多了設置超時時間。參數timeout
指定超時時間,uint
指定時間的單位,在枚舉類TimeUnit
中有相關的定義。如果計算超時,將拋出TimeoutException
3. Future
接口的使用
示例代碼:
public class FutureExample1 {
/* 定義一個callable任務 */
static class MyCallable implements Callable<String> {
@Override
public String call() throws Exception{
log.info("do something in callable");
Thread.sleep(5000);
return "Done";
}
}
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
//利用future接收Callable的返回值
Future<String> future = executorService.submit(new MyCallable());
log.info("do something in main");
Thread.sleep(5000);
String result = future.get();
log.info("result:{}",result);
}
}
返回結果:
10:43:07.620 [main] INFO com.mmall.concurrency.example.aqs.FutureExample1 - do something in main
10:43:07.621 [pool-1-thread-1] INFO com.mmall.concurrency.example.aqs.FutureExample1 - do something in callable
10:43:12.625 [main] INFO com.mmall.concurrency.example.aqs.FutureExample1 - result:Done
Process finished with exit code 0
可以看到最後兩條日誌輸出的時間,間隔了5秒鐘,符合預期的效果。
三、FutureTask
1.FutureTask
介紹
FutureTask
是J.U.C
(java.util.concurrent
的簡稱)中的類,是一個可刪除的異步計算類。這個類提供了Future
接口的的基本實現,使用相關方法啓動和取消計算,查詢計算是否完成,並檢索計算結果。只有在計算完成時才能使用get
方法檢索結果;如果計算尚未完成,get
方法將會阻塞。一旦計算完成,計算就不能重新啓動或取消(除非使用runAndReset
方法調用計算)。
Future
實現了RunnableFuture
接口,而RunnableFuture
接口繼承了Runnable
與Future
接口,所以它既可以作爲Runnable
被線程中執行,又可以作爲callable
獲得返回值。
源碼如下:
public class FutureTask<V> implements RunnableFuture<V> {
...
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
2.FutureTask
的使用
示例代碼:
@Slf4j
public class FutureTaskExample1 {
public static void main(String[] args) throws Exception {
FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
log.info("do something in callable");
Thread.sleep(5000);
return "Done";
}
});
new Thread(futureTask).start();
log.info("do something in main");
Thread.sleep(1000);
String result = futureTask.get();
log.info("result:{}",result);
}
}
執行結果:
10:52:34.076 [main] INFO com.mmall.concurrency.example.aqs.FutureTaskExample1 - do something in main
10:52:34.076 [Thread-0] INFO com.mmall.concurrency.example.aqs.FutureTaskExample1 - do something in callable
10:52:39.083 [main] INFO com.mmall.concurrency.example.aqs.FutureTaskExample1 - result:Done
Process finished with exit code 0
四、Fork/Join
框架
1.介紹
ForkJoin
是Java7
提供的一個並行執行任務的框架,是把大任務分割成若干個小任務,待小任務完成後將結果彙總成大任務結果的框架。主要採用的是工作竊取算法,工作竊取算法是指某個線程從其他隊列裏竊取任務來執行。- 在
Fork/Join
框架中,會將一個複雜任務分配到多個線程中執行,每個線程都對應一個隊列,在隊列中可能會有多個任務需要執行。當其中一個線程執行任務比較快,就會從其他線程中竊取一個任務放到自己的隊列中去執行,充分利用線程進行並行計算; - 在竊取過程中兩個線程會訪問同一個隊列,爲了減少竊取任務線程和被竊取任務線程之間的競爭,通常我們會使用雙端隊列來實現工作竊取算法。被竊取任務的線程永遠從隊列的頭部拿取任務,竊取任務的線程從隊列尾部拿取任務。
2. 優缺點
**優點:**充分利用線程進行並行計算,並減小了線程間的競爭;
**缺點:**在某些情況下,依然存在競爭,如雙端隊列中只有一個隊列時;
3.侷限性
- 任務只能使用
fork
和join
作爲同步機制,如果使用了其他同步機制,當他們在同步操作時,工作線程就不能執行其他任務了。比如在fork
框架使任務進入了睡眠,那麼在睡眠期間內在執行這個任務的線程將不會執行其他任務了。 - 我們所拆分的任務不應該去執行
IO
操作,如讀和寫數據文件。 - 任務不能拋出檢查異常。必須通過必要的代碼來處理他們。
4.框架核心
核心有兩個類:ForkJoinPool
| ForkJoinTask
ForkJoinPool
:負責來做實現,包括工作竊取算法、管理工作線程和提供關於任務的狀態以及他們的執行信息。
ForkJoinTask
:提供在任務中執行fork和join的機制。
5.Fork/Join
框架使用示例
示例代碼:
@Slf4j
public class ForkJoinTaskExample extends RecursiveTask<Integer> {
public static final int threshold = 2;//設定不大於兩個數相加就直接for循環,不適用框架
private int start;
private int end;
public ForkJoinTaskExample(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
//如果任務足夠小就計算任務
boolean canCompute = (end - start) <= threshold;
if (canCompute) {
for (int i = start; i <= end; i++) {
sum += i;
}
} else {
// 如果任務大於閾值,就分裂成兩個子任務計算(分裂算法,可依情況調優)
int middle = (start + end) / 2;
ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
// 執行子任務
leftTask.fork();
rightTask.fork();
// 等待任務執行結束合併其結果
int leftResult = leftTask.join();
int rightResult = rightTask.join();
// 合併子任務
sum = leftResult + rightResult;
}
return sum;
}
public static void main(String[] args) {
ForkJoinPool forkjoinPool = new ForkJoinPool();
//生成一個計算任務,計算1+2+3+4...100
ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
//執行一個任務
Future<Integer> result = forkjoinPool.submit(task);
try {
log.info("result:{}", result.get());
} catch (Exception e) {
log.error("exception", e);
}
}
}
五、BlockingQueue
阻塞隊列
1.應用場景
主要應用場景:生產者消費者模型,是線程安全的
2.導致阻塞的情況:
- 當隊列滿了進行入隊操作;
- 當隊列空了的時候進行出隊列操作;
3.BlockingQueue
提供的四套方法
- Throws Exceptions :如果不能立即執行就拋出異常;
- Special Value:如果不能立即執行就返回一個特殊的值;
- Blocks:如果不能立即執行就阻塞;
- Times Out:如果不能立即執行就阻塞一段時間,如果過了設定時間還沒有被執行,則返回一個值;
4.BlockingQueue
的實現類
ArrayBlockingQueue
:它是一個有界的阻塞隊列,內部實現是數組,初始化時指定容量大小,一旦指定大小就不能再變。採用FIFO
方式存儲元素。DelayQueue
:阻塞內部元素,內部元素必須實現Delayed
接口,Delayed
接口又繼承了Comparable
接口,原因在於DelayQueue
內部元素需要排序,一般情況按過期時間優先級排序。LinkedBlockingQueue
:大小配置可選,如果初始化時指定了大小,那麼它就是有邊界的。不指定就無邊界(最大整型值)。內部實現是鏈表,採用FIFO
形式保存數據。
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);//不指定大小,無邊界採用默認值,最大整型值
}
PriorityBlockingQueue
:帶優先級的阻塞隊列。無邊界隊列,允許插入null
。插入的對象必須實現Comparator
接口,隊列優先級的排序規則就是按照我們對Comparable
接口的實現來指定的。我們可以從PriorityBlockingQueue
中獲取一個迭代器,但這個迭代器並不保證能按照優先級的順序進行迭代。SynchronusQueue
:只能插入一個元素,同步隊列,無界非緩存隊列,不存儲元素。
Java併發編程學習系列
如有幫助,煩請點贊收藏一下啦 (◕ᴗ◕✿)