溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java?ThreadPoolExecutor線程池拒絕策略實例分析

發(fā)布時間:2022-07-26 09:45:55 來源:億速云 閱讀:77 作者:iii 欄目:開發(fā)技術

這篇文章主要講解了“java ThreadPoolExecutor線程池拒絕策略實例分析”,文中的講解內(nèi)容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“java ThreadPoolExecutor線程池拒絕策略實例分析”吧!

1.場景

線程池使用DiscardOldestPolicy拒絕策略,阻塞隊列使用ArrayBlockingQueue,發(fā)現(xiàn)在某些情形下對于得到的Future,調用get()方法當前線程會一直阻塞。

為了便于理解,將實際情景抽象為下面的代碼:

ThreadPoolExecutor threadPoolExecutor  = new ThreadPoolExecutor(
        1,
        1,
        1,
        TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(1),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.DiscardOldestPolicy());//新建線程池時核心線程數(shù)及最大線程數(shù)都設置為1,阻塞隊列使用ArrayBlockingQueue,拒絕策略為DiscardOldestPolicy
public void doBusiness(){
    Task task1 = new Task();
    Task task2 = new Task();
    Task task3 = new Task();
    Future<Boolean> future1 = threadPoolExecutor.submit(task1);//當前工作線程為0,會新建一個worker作為工作線程,并執(zhí)行task1
    Future<Boolean> future2 = threadPoolExecutor.submit(task2);//當前核心線程數(shù)已滿,會將任務放入阻塞隊列
    Future<Boolean> future3 = threadPoolExecutor.submit(task3);
    /*當前核心線程已滿并且阻塞隊列已滿,execute()時會調用ThreadPoolExecutord的addWorker(command,false),由
    于目前task1還沒執(zhí)行完,則工作線程數(shù)量為1,已經(jīng)達到了最大線程數(shù),則addWorker(command,false)返回false,
    觸發(fā)對應的拒絕策略,會從阻塞隊列中移除task2對應的任務(阻塞隊列中并不是直接放的task2,而是以task2為入
    參構造的一個FutureTask,參見AbstarctExecutorService的submit(Callable<T> task)方法*/
    try{
        boolean result = future2.get();
        System.out.println(result);
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
@Test
public void test_doBusiness(){
    doBusiness();//入口
}
private class Task implements Callable<Boolean>{
    @Override
    public Boolean call() throws Exception {
        try {
            Thread.sleep(1000);//模擬業(yè)務執(zhí)行
            return true;
        }catch(Exception e){
            e.printStackTrace();
        }
        return true;
    }
}

2. 原因分析

通過上面代碼我們明白了阻塞隊列會將task2對應的任務移除,那么為何移除之后調用get()方法線程會一直阻塞呢?

其實Future future2= threadPoolExecutor.submit(task2)實際會調用AbstractExecutorService的submit(Callable task)方法,并且最終返回的future2實際是一個FutureTask類型。

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

因此,我們直接看FutureTask的get()方法

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s &lt;= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}

由于future2已經(jīng)從阻塞隊列中移除,并且從始至終都沒有工作線程執(zhí)行它,即FutureTask的狀態(tài)一直都為NEW狀態(tài),其會進入awaitDone(false,0L)中,接下列我們追蹤該方法。

private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }
        int s = state;
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        else if (q == null)//第一次進for循環(huán)時q==null,進入到該分支
            q = new WaitNode();
        else if (!queued)//第二次進for循環(huán)時queue為false,則使用CAS將q置為waiters的頭結點
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                 q.next = waiters, q);
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else//將q置為頭結點后,最終會進入這里調用park()方法,阻塞當前線程
            LockSupport.park(this);
    }

從上面的代碼可以看出調用future2.get()后會一直阻塞在park()方法處,這便是本次問題出現(xiàn)的原因,

3.總結

本次問題出現(xiàn)主要是同時滿足了以下幾點:

1)使用了有界的阻塞隊列ArrayBlockingQueue

2)工作線程達到了線程池配置的最大線程數(shù)

3)拒絕策略使用了DiscardOldestPolicy(使用DiscardPolicy也會出現(xiàn)這個問題)

4.思考

我們?nèi)粘J褂镁€程池提交任務后,如果在任務執(zhí)行完成之前調用future的get()方法,當前線程會進入阻塞狀態(tài),當任務執(zhí)行完成后,才會將當前線程喚醒,如何從代碼上分析該流程?

首先當任務提交到線程池,如果任務當前在阻塞隊列中,則FutureTask的狀態(tài)依然像上面的情況一樣,是處于New狀態(tài),調用get()方法依然會到達LockSupport.park(this)處,將當前線程阻塞。什么時候才會將當前線程喚醒了?

那就是當存在工作線程Worker目前分配的任務執(zhí)行完成后,其會去調用Worker類的getTask()方法從阻塞隊列中拿到該任務,并執(zhí)行該任務的run()方法,下面是FutureTask的run()方法

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);//如果任務執(zhí)行成功,則調用set(V 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);
    }
}

其會在執(zhí)行成功后,調用set(V result)方法

protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();//
    }
}

然后將FutureTask狀態(tài)置為NORMAL(FutureTask的狀態(tài)要和ThreadPoolExecutor的狀態(tài)區(qū)分開),接著調用finishCompletion()方法

private void finishCompletion() {
    // assert state > COMPLETING;
    for (WaitNode q; (q = waiters) != null;) {
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            for (;;) {
                Thread t = q.thread;//q在await()方法中設置的,其值為調用get()方法的線程
                if (t != null) {
                    q.thread = null;
                    LockSupport.unpark(t);//喚醒該線程
                }
                WaitNode next = q.next;
                if (next == null)
                    break;
                q.next = null; // unlink to help gc
                q = next;
            }
            break;
        }
    }
    done();//熟悉的鉤子方法
    callable = null;        // to reduce footprint
}

在finishCompletion中喚起因get()而阻塞的線程。

感謝各位的閱讀,以上就是“java ThreadPoolExecutor線程池拒絕策略實例分析”的內(nèi)容了,經(jīng)過本文的學習后,相信大家對java ThreadPoolExecutor線程池拒絕策略實例分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI