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
  • 相关阅读:
    hdu acm 2844 Coins 解题报告
    hdu 1963 Investment 解题报告
    codeforces 454B. Little Pony and Sort by Shift 解题报告
    广大暑假训练1 E题 Paid Roads(poj 3411) 解题报告
    hdu acm 2191 悼念512汶川大地震遇难同胞——珍惜现在,感恩生活
    hdu acm 1114 Piggy-Bank 解题报告
    poj 2531 Network Saboteur 解题报告
    数据库范式
    ngnix 配置CI框架 与 CI的简单使用
    Vundle的安装
  • 原文地址:https://www.cnblogs.com/jpfss/p/11016180.html
Copyright © 2011-2022 走看看