zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    Go的Web之旅【部署】
    module declares its path as: github.com/xxx/yyy【Go报错】
    android studio快捷键集合
    Web App 概述
    WIN10 kafka搭建完后命令测试步骤自用
    Echarts tab 切换解决图不能显示的问题
    如何为不定高度(height:auto)的元素添加CSS3 transition-property:height 动画
    手持设备开发项目实例
    SQL Server 查找表问题
    Tomcat 10 升级注意事项
  • 原文地址:https://www.cnblogs.com/jpfss/p/11016180.html
Copyright © 2011-2022 走看看