線程池的集中拒絕策略,很實用

原文地址 http://blog.csdn.net/qq_25806863/article/details/71172823

在分析ThreadPoolExecutor的構造參數時,有一個RejectedExecutionHandler參數。

RejectedExecutionHandler是一個接口:

 

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

裏面只有一個方法。當要創建的線程數量大於線程池的最大線程數的時候,新的任務就會被拒絕,就會調用這個接口裏的這個方法。

可以自己實現這個接口,實現對這些超出數量的任務的處理。

ThreadPoolExecutor自己已經提供了四個拒絕策略,分別是CallerRunsPolicy,AbortPolicy,DiscardPolicy,DiscardOldestPolicy

這四個拒絕策略其實一看實現方法就知道很簡單。

AbortPolicy

ThreadPoolExecutor中默認的拒絕策略就是AbortPolicy。直接拋出異常。

 

private static final RejectedExecutionHandler defaultHandler =
    new AbortPolicy();

下面是他的實現:

 

public static class AbortPolicy implements RejectedExecutionHandler {
    public AbortPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}

很簡單粗暴,直接拋出個RejectedExecutionException異常,也不執行這個任務了。

測試

先自定義一個Runnable,給每個線程起個名字,下面都用這個Runnable

 

static class MyThread implements Runnable {
        String name;
        public MyThread(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("線程:"+Thread.currentThread().getName() +" 執行:"+name +"  run");
        }
    }

然後構造一個核心線程是1,最大線程數是2的線程池。拒絕策略是AbortPolicy

 

ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 0, 
        TimeUnit.MICROSECONDS, 
        new LinkedBlockingDeque<Runnable>(2), 
        new ThreadPoolExecutor.AbortPolicy());

 

for (int i = 0; i < 6; i++) {
    System.out.println("添加第"+i+"個任務");
    executor.execute(new MyThread("線程"+i));
    Iterator iterator = executor.getQueue().iterator();
    while (iterator.hasNext()){
        MyThread thread = (MyThread) iterator.next();
        System.out.println("列表:"+thread.name);
    }
}

輸出是:

分析一下過程。

  1. 添加第一個任務時,直接執行,任務列表爲空。
  2. 添加第二個任務時,因爲採用的LinkedBlockingDeque,,並且核心線程正在執行任務,所以會將第二個任務放在隊列中,隊列中有 線程2.
  3. 添加第三個任務時,也一樣會放在隊列中,隊列中有 線程2,線程3.
  4. 添加第四個任務時,因爲核心任務還在運行,而且任務隊列已經滿了,所以胡直接創建新線程執行第四個任務,。這時線程池中一共就有兩個線程在運行了,達到了最大線程數。任務隊列中還是有線程2, 線程3.
  5. 添加第五個任務時,再也沒有地方能存放和執行這個任務了,就會被線程池拒絕添加,執行拒絕策略的rejectedExecution方法,這裏就是執行AbortPolicy的rejectedExecution方法直接拋出異常。
  6. 最終,只有四個線程能完成運行。後面的都被拒絕了。

CallerRunsPolicy

CallerRunsPolicy在任務被拒絕添加後,會調用當前線程池的所在的線程去執行被拒絕的任務。

下面說他的實現:

 

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public CallerRunsPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

也很簡單,直接run。

測試

 

ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 30,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<Runnable>(2),
        new ThreadPoolExecutor.AbortPolicy());

按上面的運行,輸出

注意在添加第五個任務,任務5 的時候,同樣被線程池拒絕了,因此執行了CallerRunsPolicy的rejectedExecution方法,這個方法直接執行任務的run方法。因此可以看到任務5是在main線程中執行的。

從中也可以看出,因爲第五個任務在主線程中運行,所以主線程就被阻塞了,以至於當第五個任務執行完,添加第六個任務時,前面兩個任務已經執行完了,有了空閒線程,因此線程6又可以添加到線程池中執行了。

這個策略的缺點就是可能會阻塞主線程。

DiscardPolicy

這個策略的處理就更簡單了,看一下實現就明白了:

 

public static class DiscardPolicy implements RejectedExecutionHandler {
    public DiscardPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}

這個東西什麼都沒幹。

因此採用這個拒絕策略,會讓被線程池拒絕的任務直接拋棄,不會拋異常也不會執行。

測試

 

ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 30,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<Runnable>(2),
        new ThreadPoolExecutor.DiscardPolicy());

輸出:

可以看到 後面添加的任務5和6根本不會執行,什麼反應都沒有,直接丟棄。

DiscardOldestPolicy

DiscardOldestPolicy策略的作用是,當任務唄拒絕添加時,會拋棄任務隊列中最舊的任務也就是最先加入隊列的,再把這個新任務添加進去。

 

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public DiscardOldestPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

在rejectedExecution先從任務隊列總彈出最先加入的任務,空出一個位置,然後再次執行execute方法把任務加入隊列。

測試

 

ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 30,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<Runnable>(2),
        new ThreadPoolExecutor.DiscardOldestPolicy());

輸出是:

可以看到,

  1. 在添加第五個任務時,會被線程池拒絕。這時任務隊列中有 任務2,任務3
  2. 這時,拒絕策略會讓任務隊列中最先加入的任務彈出,也就是任務2.
  3. 然後把被拒絕的任務5添加人任務隊列,這時任務隊列中就成了 任務3,任務5.
  4. 添加第六個任務時會因爲同樣的過程,將隊列中的任務3拋棄,把任務6加進去,任務隊列中就成了 任務5,任務6
  5. 因此,最終能被執行的任務只有1,4,5,6. 任務2和任務3倍拋棄了,不會執行。

自定義拒絕策略

通過看前面的系統提供的四種拒絕策略可以看出,拒絕策略的實現都非常簡單。自己寫亦一樣

比如現在想讓被拒絕的任務在一個新的線程中執行,可以這樣寫:

 

static class MyRejectedExecutionHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        new Thread(r,"新線程"+new Random().nextInt(10)).start();
    }
}

然後正常使用:

 

ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 30,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<Runnable>(2),
        new MyRejectedExecutionHandler());

輸出:

發現被拒絕的任務5和任務6都在新線程中執行了。

 

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