Java並發(五)線程池使用番外-分析RejectedExecutionException異常


目錄

  一、入門示例

  二、異常場景1

  三、異常場景2

  四、解決方法

 

之前在使用線程池的時候,出現了 java.util.concurrent.RejectedExecutionException ,原因是線程池配置不合理,導致提交的任務來不及處理。接下來用一個簡單的例子來復現異常。

復制代碼
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task org.cellphone.common.pool.Worker@f6f4d33 rejected from java.util.concurrent.ThreadPoolExecutor@23fc625e[Running, pool size = 3, active threads = 3, queued tasks = 15, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
    at org.cellphone.common.pool.RejectedExecutionExceptionExample.main(RejectedExecutionExceptionExample.java:22)
復制代碼

一、入門示例

下面的測試程序使用 ThreadPoolExecutor 類來創建線程池執行任務,代表任務 Worker 類代碼如下:

復制代碼
/**
 * Created by on 2019/4/20.
 */
public class Worker implements Runnable {
</span><span style="color: #0000ff;">private</span> <span style="color: #0000ff;">int</span><span style="color: #000000;"> id;

</span><span style="color: #0000ff;">public</span> Worker(<span style="color: #0000ff;">int</span><span style="color: #000000;"> id) {
    </span><span style="color: #0000ff;">this</span>.id =<span style="color: #000000;"> id;
}

@Override
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> run() {
    </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
        System.out.println(Thread.currentThread().getName() </span>+ " 執行任務 " +<span style="color: #000000;"> id);
        Thread.sleep(</span>1000<span style="color: #000000;">);
        System.out.println(Thread.currentThread().getName() </span>+ " 完成任務 " +<span style="color: #000000;"> id);
    } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Exception e) {
        e.printStackTrace();
    }
}

}

復制代碼

 執行 Worker 任務的代碼如下:

復制代碼
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**

  • Created by on 2019/4/20.
    */
    public class RejectedExecutionExceptionExample {

    public static void main(String[] args) {

     ExecutorService executor </span>= <span style="color: #0000ff;">new</span> ThreadPoolExecutor(3, 3, 0L<span style="color: #000000;">,
             TimeUnit.MILLISECONDS, </span><span style="color: #0000ff;">new</span> ArrayBlockingQueue&lt;&gt;(15<span style="color: #000000;">));
    
     Worker tasks[] </span>= <span style="color: #0000ff;">new</span> Worker[10<span style="color: #000000;">];
     </span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i &lt; 10; i++<span style="color: #000000;">) {
         tasks[i] </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> Worker(i);
         System.out.println("提交任務: " + tasks[i] </span>+ ", " +<span style="color: #000000;"> i);
         executor.execute(tasks[i]);
     }
     System.out.println(</span>"主線程結束"<span style="color: #000000;">);
     executor.shutdown();    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 關閉線程池</span>
    

}
}

復制代碼

 運行一下,看到如下輸出:

復制代碼
提交任務: org.cellphone.common.pool.Worker@36baf30c, 0
提交任務: org.cellphone.common.pool.Worker@5ca881b5, 1
提交任務: org.cellphone.common.pool.Worker@4517d9a3, 2
提交任務: org.cellphone.common.pool.Worker@2f92e0f4, 3
提交任務: org.cellphone.common.pool.Worker@28a418fc, 4
提交任務: org.cellphone.common.pool.Worker@5305068a, 5
提交任務: org.cellphone.common.pool.Worker@1f32e575, 6
提交任務: org.cellphone.common.pool.Worker@279f2327, 7
提交任務: org.cellphone.common.pool.Worker@2ff4acd0, 8
提交任務: org.cellphone.common.pool.Worker@54bedef2, 9
主線程結束
pool-1-thread-1 執行任務 0
pool-1-thread-2 執行任務 1
pool-1-thread-3 執行任務 2
pool-1-thread-1 完成任務 0
pool-1-thread-1 執行任務 3
pool-1-thread-2 完成任務 1
pool-1-thread-2 執行任務 4
pool-1-thread-3 完成任務 2
pool-1-thread-3 執行任務 5
pool-1-thread-2 完成任務 4
pool-1-thread-2 執行任務 6
pool-1-thread-3 完成任務 5
pool-1-thread-1 完成任務 3
pool-1-thread-3 執行任務 7
pool-1-thread-1 執行任務 8
pool-1-thread-3 完成任務 7
pool-1-thread-2 完成任務 6
pool-1-thread-1 完成任務 8
pool-1-thread-2 執行任務 9
pool-1-thread-2 完成任務 9
復制代碼

 在 RejectedExecutionExceptionExample 類里,我們使用 ThreadPoolExecutor 類創建了一個數量為3的線程池來執行任務,在這3個線程執行任務被占用期間,如果有新任務提交給線程池,那么這些新任務會被保存在 BlockingQueue 阻塞隊列里,以等待被空閑線程取出並執行。在這里我們使用一個大小為15的 ArrayBlockingQueue 隊列來保存待執行的任務,然后我們創建了10個任務提交給 ThreadPoolExecutor 線程池。

 二、異常場景1

產生 RejectedExecutionException 異常的第一個原因:

調用 shutdown() 方法關閉了 ThreadPoolExecutor 線程池,又提交新任務給 ThreadPoolExecutor 線程池執行。一般調用 shutdown() 方法之后,JVM會得到一個關閉線程池的信號,並不會立即關閉線程池,原來線程池里未執行完的任務仍然在執行,等到任務都執行完后才關閉線程池,但是JVM不允許再提交新任務給線程池。

讓我們用以下例子來重現該異常:

復制代碼
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**

  • Created by on 2019/4/20.
    */
    public class RejectedExecutionExceptionExample {

    public static void main(String[] args) {

     ExecutorService executor </span>= <span style="color: #0000ff;">new</span> ThreadPoolExecutor(3, 3, 0L<span style="color: #000000;">,
             TimeUnit.MILLISECONDS, </span><span style="color: #0000ff;">new</span> ArrayBlockingQueue&lt;&gt;(15<span style="color: #000000;">));
    
     Worker tasks[] </span>= <span style="color: #0000ff;">new</span> Worker[10<span style="color: #000000;">];
     </span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i &lt; 10; i++<span style="color: #000000;">) {
         tasks[i] </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> Worker(i);
         System.out.println(</span>"提交任務: " + tasks[i] + ", " +<span style="color: #000000;"> i);
         executor.execute(tasks[i]);
     }
     System.out.println(</span>"主線程結束"<span style="color: #000000;">);
     executor.shutdown();</span><span style="color: #008000;">//</span><span style="color: #008000;"> 關閉線程池</span>
     executor.execute(tasks[0]);<span style="color: #008000;">//</span><span style="color: #008000;"> 關閉線程池之后提交新任務,運行之后拋異常</span>
    

}
}

復制代碼

運行一下,看到如下輸出:

復制代碼
提交任務: org.cellphone.common.pool.Worker@36baf30c, 0
提交任務: org.cellphone.common.pool.Worker@5ca881b5, 1
提交任務: org.cellphone.common.pool.Worker@4517d9a3, 2
提交任務: org.cellphone.common.pool.Worker@2f92e0f4, 3
提交任務: org.cellphone.common.pool.Worker@28a418fc, 4
提交任務: org.cellphone.common.pool.Worker@5305068a, 5
提交任務: org.cellphone.common.pool.Worker@1f32e575, 6
提交任務: org.cellphone.common.pool.Worker@279f2327, 7
提交任務: org.cellphone.common.pool.Worker@2ff4acd0, 8
提交任務: org.cellphone.common.pool.Worker@54bedef2, 9
主線程結束
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task org.cellphone.common.pool.Worker@36baf30c rejected from java.util.concurrent.ThreadPoolExecutor@5caf905d[Shutting down, pool size = 3, active threads = 3, queued tasks = 7, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
    at org.cellphone.common.pool.RejectedExecutionExceptionExample.main(RejectedExecutionExceptionExample.java:26)
pool-1-thread-1 執行任務 0
pool-1-thread-2 執行任務 1
pool-1-thread-3 執行任務 2
pool-1-thread-1 完成任務 0
pool-1-thread-1 執行任務 3
pool-1-thread-2 完成任務 1
pool-1-thread-2 執行任務 4
pool-1-thread-3 完成任務 2
pool-1-thread-3 執行任務 5
pool-1-thread-3 完成任務 5
pool-1-thread-3 執行任務 6
pool-1-thread-2 完成任務 4
pool-1-thread-2 執行任務 7
pool-1-thread-1 完成任務 3
pool-1-thread-1 執行任務 8
pool-1-thread-3 完成任務 6
pool-1-thread-2 完成任務 7
pool-1-thread-3 執行任務 9
pool-1-thread-1 完成任務 8
pool-1-thread-3 完成任務 9
復制代碼

從以上例子可以看出,在調用 shutdown() 方法之后,由於JVM不允許再提交新任務給線程池,於是拋出了 RejectedExecutionException 異常。

三、異常場景2

產生 RejectedExecutionException 異常第二個原因:

要提交給阻塞隊列的任務超出了該隊列的最大容量。當線程池里的線程都繁忙的時候,新任務會被提交給阻塞隊列保存,這個阻塞隊列一旦飽和,線程池就會拒絕接收新任務,隨即拋出異常。

示例代碼如下:

復制代碼
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**

  • Created by on 2019/4/20.
    */
    public class RejectedExecutionExceptionExample {

    public static void main(String[] args) {

     ExecutorService executor </span>= <span style="color: #0000ff;">new</span> ThreadPoolExecutor(3, 3, 0L<span style="color: #000000;">,
             TimeUnit.MILLISECONDS, </span><span style="color: #0000ff;">new</span> ArrayBlockingQueue&lt;&gt;(15<span style="color: #000000;">));
    
     </span><span style="color: #008000;">//</span><span style="color: #008000;"> 提交20個任務給線程池</span>
     Worker tasks[] = <span style="color: #0000ff;">new</span> Worker[20<span style="color: #000000;">];
     </span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i &lt; 20; i++<span style="color: #000000;">) {
         tasks[i] </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> Worker(i);
         System.out.println(</span>"提交任務: " + tasks[i] + ", " +<span style="color: #000000;"> i);
         executor.execute(tasks[i]);
     }
     System.out.println(</span>"主線程結束"<span style="color: #000000;">);
     executor.shutdown();</span><span style="color: #008000;">//</span><span style="color: #008000;"> 關閉線程池</span>
    

}
}

復制代碼

在上面的例子中,我們使用了一個大小為15的 ArrayBlockingQueue 阻塞隊列來保存等待執行的任務。接着我們提交了20個任務給線程池,由於每個線程執行任務的時候會睡眠1秒,因此當3個線程繁忙的時候,其他任務不會立即得到執行,我們提交的新任務會被保存在隊列里。當等待任務的數量超過線程池阻塞隊列的最大容量時,拋出了 RejectedExecutionException 異常。

四、解決方法

要解決 RejectedExecutionException 異常,首先我們要注意兩種情況:

  1. 當調用了線程池的shutdown()方法以后,不要提交新任務給線程池
  2. 不要提交大量超過線程池處理能力的任務,這時可能會導致隊列飽和,拋出異常

對於第二種情況,我們很容易解決。我們可以選擇一種不需要設置大小限制的數據結構,比如 LinkedBlockingQueue 阻塞隊列。因此在使用 LinkedBlockingQueue 隊列以后,如果還出現 RejectedExecutionException 異常,就要將問題的重點放在第一種情況上。如果第一種情況不是產生問題的原因,那么我們還需要尋找更復雜的原因。比如,由於線程死鎖和 LinkedBlockingQueue 飽和,導致內存占用過大,這個時候我們就需要考慮JVM可用內存的問題了。

對於第二種情況,通常有一些隱藏的信息被我們忽略。其實我們可以給使用 ArrayBlockingQueue 作為阻塞隊列的 ThreadPoolExecutor 線程池提交超過15個的任務,只要我們在提交新任務前設置一個完成原來任務的等待時間,這時3個線程就會逐漸消費 ArrayBlockingQueue 阻塞隊列里的任務,而不會使它堵塞。示例如下:

復制代碼
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**

  • Created by on 2019/4/20.
    */
    public class RejectedExecutionExceptionExample {

    public static void main(String[] args) throws InterruptedException {

     ExecutorService executor </span>= <span style="color: #0000ff;">new</span> ThreadPoolExecutor(3, 3, 0L<span style="color: #000000;">,
             TimeUnit.MILLISECONDS, </span><span style="color: #0000ff;">new</span> ArrayBlockingQueue&lt;&gt;(15<span style="color: #000000;">));
    
     </span><span style="color: #008000;">//</span><span style="color: #008000;"> 提交20個任務給線程池</span>
     Worker tasks[] = <span style="color: #0000ff;">new</span> Worker[20<span style="color: #000000;">];
     </span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i &lt; 10; i++<span style="color: #000000;">) {
         tasks[i] </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> Worker(i);
         System.out.println(</span>"提交任務: " + tasks[i] + ", " +<span style="color: #000000;"> i);
         executor.execute(tasks[i]);
     }
    
     Thread.sleep(</span>3000);<span style="color: #008000;">//</span><span style="color: #008000;"> 讓主線程睡眠三秒</span>
     <span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 10; i &lt; 20; i++<span style="color: #000000;">) {
         tasks[i] </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> Worker(i);
         System.out.println(</span>"提交任務: " + tasks[i] + ", " +<span style="color: #000000;"> i);
         executor.execute(tasks[i]);
     }
    
     System.out.println(</span>"主線程結束"<span style="color: #000000;">);
     executor.shutdown();</span><span style="color: #008000;">//</span><span style="color: #008000;"> 關閉線程池</span>
    

}
}

復制代碼

運行一下,看到如下輸出:

復制代碼
提交任務: org.cellphone.common.pool.Worker@36baf30c, 0
提交任務: org.cellphone.common.pool.Worker@5ca881b5, 1
提交任務: org.cellphone.common.pool.Worker@4517d9a3, 2
提交任務: org.cellphone.common.pool.Worker@2f92e0f4, 3
提交任務: org.cellphone.common.pool.Worker@28a418fc, 4
提交任務: org.cellphone.common.pool.Worker@5305068a, 5
提交任務: org.cellphone.common.pool.Worker@1f32e575, 6
提交任務: org.cellphone.common.pool.Worker@279f2327, 7
提交任務: org.cellphone.common.pool.Worker@2ff4acd0, 8
提交任務: org.cellphone.common.pool.Worker@54bedef2, 9
pool-1-thread-1 執行任務 0
pool-1-thread-2 執行任務 1
pool-1-thread-3 執行任務 2
pool-1-thread-2 完成任務 1
pool-1-thread-3 完成任務 2
pool-1-thread-1 完成任務 0
pool-1-thread-3 執行任務 4
pool-1-thread-2 執行任務 3
pool-1-thread-1 執行任務 5
pool-1-thread-3 完成任務 4
pool-1-thread-3 執行任務 6
pool-1-thread-2 完成任務 3
pool-1-thread-2 執行任務 7
pool-1-thread-1 完成任務 5
pool-1-thread-1 執行任務 8
提交任務: org.cellphone.common.pool.Worker@5caf905d, 10
提交任務: org.cellphone.common.pool.Worker@27716f4, 11
提交任務: org.cellphone.common.pool.Worker@8efb846, 12
提交任務: org.cellphone.common.pool.Worker@2a84aee7, 13
提交任務: org.cellphone.common.pool.Worker@a09ee92, 14
提交任務: org.cellphone.common.pool.Worker@30f39991, 15
提交任務: org.cellphone.common.pool.Worker@452b3a41, 16
提交任務: org.cellphone.common.pool.Worker@4a574795, 17
提交任務: org.cellphone.common.pool.Worker@f6f4d33, 18
pool-1-thread-3 完成任務 6
pool-1-thread-2 完成任務 7
pool-1-thread-1 完成任務 8
pool-1-thread-2 執行任務 10
pool-1-thread-3 執行任務 9
提交任務: org.cellphone.common.pool.Worker@23fc625e, 19
pool-1-thread-1 執行任務 11
主線程結束
pool-1-thread-2 完成任務 10
pool-1-thread-2 執行任務 12
pool-1-thread-1 完成任務 11
pool-1-thread-1 執行任務 13
pool-1-thread-3 完成任務 9
pool-1-thread-3 執行任務 14
pool-1-thread-2 完成任務 12
pool-1-thread-2 執行任務 15
pool-1-thread-3 完成任務 14
pool-1-thread-3 執行任務 16
pool-1-thread-1 完成任務 13
pool-1-thread-1 執行任務 17
pool-1-thread-2 完成任務 15
pool-1-thread-2 執行任務 18
pool-1-thread-3 完成任務 16
pool-1-thread-1 完成任務 17
pool-1-thread-3 執行任務 19
pool-1-thread-2 完成任務 18
pool-1-thread-3 完成任務 19
復制代碼

當然上面這種設置等待時間來分隔舊任務和新任務的方式,在高並發情況下效率並不高,一方面由於我們無法准確預估等待時間,一方面由於 ArrayBlockingQueue 內部只使用了一個鎖來隔離讀和寫的操作,因此效率沒有使用了兩個鎖來隔離讀寫操作的 LinkedBlockingQueue 高,故而不推薦使用這種方式。

原文地址:https://www.cnblogs.com/warehouse/p/10746939.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM