1、入門示例java
2、異常場景1數據結構
3、異常場景2併發
4、解決方法ide
以前在使用線程池的時候,出現了 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 { private int id; public Worker(int id) { this.id = id; } @Override public void run() { try { System.out.println(Thread.currentThread().getName() + " 執行任務 " + id); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " 完成任務 " + id); } catch (Exception e) { e.printStackTrace(); } } }
執行 Worker 任務的代碼以下:this
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 = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(15)); Worker tasks[] = new Worker[10]; for (int i = 0; i < 10; i++) { tasks[i] = new Worker(i); System.out.println("提交任務: " + tasks[i] + ", " + i); executor.execute(tasks[i]); } System.out.println("主線程結束"); executor.shutdown(); // 關閉線程池 } }
運行一下,看到以下輸出:spa
提交任務: 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 線程池。線程
產生 RejectedExecutionException 異常的第一個緣由:code
調用 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 = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(15)); Worker tasks[] = new Worker[10]; for (int i = 0; i < 10; i++) { tasks[i] = new Worker(i); System.out.println("提交任務: " + tasks[i] + ", " + i); executor.execute(tasks[i]); } System.out.println("主線程結束"); executor.shutdown();// 關閉線程池 executor.execute(tasks[0]);// 關閉線程池以後提交新任務,運行以後拋異常 } }
運行一下,看到以下輸出:
提交任務: 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 異常。
產生 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 = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(15)); // 提交20個任務給線程池 Worker tasks[] = new Worker[20]; for (int i = 0; i < 20; i++) { tasks[i] = new Worker(i); System.out.println("提交任務: " + tasks[i] + ", " + i); executor.execute(tasks[i]); } System.out.println("主線程結束"); executor.shutdown();// 關閉線程池 } }
在上面的例子中,咱們使用了一個大小爲15的 ArrayBlockingQueue 阻塞隊列來保存等待執行的任務。接着咱們提交了20個任務給線程池,因爲每一個線程執行任務的時候會睡眠1秒,所以當3個線程繁忙的時候,其餘任務不會當即獲得執行,咱們提交的新任務會被保存在隊列裏。當等待任務的數量超過線程池阻塞隊列的最大容量時,拋出了 RejectedExecutionException 異常。
要解決 RejectedExecutionException 異常,首先咱們要注意兩種狀況:
shutdown()
方法之後,不要提交新任務給線程池對於第二種狀況,咱們很容易解決。咱們能夠選擇一種不須要設置大小限制的數據結構,好比 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 = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(15)); // 提交20個任務給線程池 Worker tasks[] = new Worker[20]; for (int i = 0; i < 10; i++) { tasks[i] = new Worker(i); System.out.println("提交任務: " + tasks[i] + ", " + i); executor.execute(tasks[i]); } Thread.sleep(3000);// 讓主線程睡眠三秒 for (int i = 10; i < 20; i++) { tasks[i] = new Worker(i); System.out.println("提交任務: " + tasks[i] + ", " + i); executor.execute(tasks[i]); } System.out.println("主線程結束"); executor.shutdown();// 關閉線程池 } }
運行一下,看到以下輸出:
提交任務: 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 高,故而不推薦使用這種方式。