Java併發編程——J.U.C組件FutureTask、ForkJoin、BlockingQueue

一、RunnableCallable

1.RunnableCallable對比

通常實現一個線程我們會使用繼承Thread的方式或者實現Runnable接口,這兩種方式有一個共同的缺陷就是在執行完任務之後無法獲取執行結果。從Java1.5之後就提供了Callable與Future,這兩個接口就可以實現獲取任務執行結果。

  • 源碼區別:

  1. Callable接口`:
    public interface Callable<V> {
        V call() throws Exception;
    }
    
  2. Runnable接口
    public interface Runnable {
        public abstract void run();
    }
    
  • 相同點

  1. 都是接口;
  2. 都可以編寫多線程程序;
  3. 都採用Thread.start()啓動線程;
  • 不同點:

  1. Runnable沒有返回值;Callable可以返回執行結果,是個泛型,和FutureFutureTask配合可以用來獲取異步執行的結果
  2. Callable接口的call()方法允許拋出異常;Runnablerun()方法異常只能在內部消化,不能往上繼續拋
注:Callalbe接口支持返回執行結果,需要調用FutureTask.get()得到,此方法會阻塞主進程的繼續往下執行,如果不調用不會阻塞;

二、Future接口

1.Future接口

  • Future介紹:Future接口是Java線程Future模式的實現,可以來進行異步計算。

  1. Future接口提供方法來檢測任務是否被執行完,等待任務執行完獲得結果,也可以設置任務執行的超時時間。這個設置超時的方法就是實現Java程序執行超時的關鍵。
  2. 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介紹

FutureTaskJ.U.Cjava.util.concurrent的簡稱)中的類,是一個可刪除的異步計算類。這個類提供了Future接口的的基本實現,使用相關方法啓動和取消計算,查詢計算是否完成,並檢索計算結果。只有在計算完成時才能使用get方法檢索結果;如果計算尚未完成,get方法將會阻塞。一旦計算完成,計算就不能重新啓動或取消(除非使用runAndReset方法調用計算)。

Future實現了RunnableFuture接口,而RunnableFuture接口繼承了RunnableFuture接口,所以它既可以作爲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.介紹

  • ForkJoinJava7提供的一個並行執行任務的框架,是把大任務分割成若干個小任務,待小任務完成後將結果彙總成大任務結果的框架。主要採用的是工作竊取算法,工作竊取算法是指某個線程從其他隊列裏竊取任務來執行。
  • Fork/Join框架中,會將一個複雜任務分配到多個線程中執行,每個線程都對應一個隊列,在隊列中可能會有多個任務需要執行。當其中一個線程執行任務比較快,就會從其他線程中竊取一個任務放到自己的隊列中去執行,充分利用線程進行並行計算;
  • 在竊取過程中兩個線程會訪問同一個隊列,爲了減少竊取任務線程和被竊取任務線程之間的競爭,通常我們會使用雙端隊列來實現工作竊取算法。被竊取任務的線程永遠從隊列的頭部拿取任務,竊取任務的線程從隊列尾部拿取任務。
    在這裏插入圖片描述

2. 優缺點

**優點:**充分利用線程進行並行計算,並減小了線程間的競爭;
**缺點:**在某些情況下,依然存在競爭,如雙端隊列中只有一個隊列時;

3.侷限性

  1. 任務只能使用forkjoin作爲同步機制,如果使用了其他同步機制,當他們在同步操作時,工作線程就不能執行其他任務了。比如在fork框架使任務進入了睡眠,那麼在睡眠期間內在執行這個任務的線程將不會執行其他任務了。
  2. 我們所拆分的任務不應該去執行IO操作,如讀和寫數據文件。
  3. 任務不能拋出檢查異常。必須通過必要的代碼來處理他們。

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.導致阻塞的情況:

  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併發編程學習系列

如有幫助,煩請點贊收藏一下啦 (◕ᴗ◕✿)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章