ThreadPoolExecutor线程池中线程不能超过核心线程数量的问题

		int arg1=2;//核心线程
        int arg2=40;//最大线程数量
        int arg3=100;//空余保留时间
        																		// 时间单位
        ThreadPoolExecutor pool=new ThreadPoolExecutor(arg1, arg2, arg3,TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());//默认构造的队列大小为Integer.Max, 可指定大小new LinkedBlockingQueue<Runnable>(3),队列容量为3
        for(int i=0;i<10;i++){
           pool.execute(new Mythread(String.valueOf(i))); 
     }
     

在自定义的Mythread类的run方法中休眠一段时间(5秒),来模拟执行的任务,我原以为循环了10次,执行10个任务,由于小于最大线程数量40,线程池应该会新建10个线程来执行这10个任务,但是实验结果发现线程池中只有2个线程(即两个核心线程)来执行这10个任务,所以共执行了5*10/2=25s时间,而不是我想象中的5s。
  原因在于,LinkedBlockingQueue的容量默认大小是Integer.Max,在任务没有填满这个容量之前线程池大小是不会超过设定的核心线程数量2的。
  当制定LinkedBlockingQueue长度为3时,这10个线程任务有两个在核心线程中执行,有3个放在任务队列中,另外5个任务将另起新线程来执行。
  
总结来说:核心线程满了,接下来进队列,队列也满了,创建新线程,直到达到最大线程数,之后再超出,会进入拒绝rejectedExecution


补充:

1、当提交一个新任务到线程池时首先线程池判断基本线程池(corePoolSize)是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程;其次线程池判断工作队列(workQueue)是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程;最后线程池判断整个线程池(maximumPoolSize)是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务;如果线程池中的线程数量大于 corePoolSize 时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过 keepAliveTime,线程也会被终止。

2、饱和策略:

Abort策略:默认策略,新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获。

CallerRuns策略:为调节机制,既不抛弃任务也不抛出异常,而是将某些任务回退到调用者。不会在线程池的线程中执行新的任务,而是在调用exector的线程中运行新的任务。

Discard策略:新提交的任务被抛弃。


(重点)证明:

package www.itbac.com;
import java.util.concurrent.*;
public class ExecutorTest {
    
    public static void main(String[] args)   {
        // 创建线程池 , 参数含义 :(核心线程数,最大线程数,加开线程的存活时间,时间单位,任务队列长度)
        ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 8,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(2));
        
        //设置:任务数 = 3 ~ 11 ,分析:任务数 与 活跃线程数,核心线程数,队列长度,最大线程数的关系。

        int a = 3;
    
            for (int i = 1; i <= a; i++) {
                int j = i;
                pool.submit(new Runnable() {
                    @Override
                    public void run() {
                        //获取线程名称
                        Thread thread = Thread.currentThread();
                        String name = thread.getName();
                        //输出
                        int activeCount = pool.getActiveCount();
                        System.out.println("任务:"+j+"-----,线程名称:"+name+"-----活跃线程数:"+activeCount);
                    }
                });
            }
            
        //关闭线程池
        pool.shutdown();

    }
}

输出结果,观察关系:

//任务数 a = 3 , 活跃线程数3 , 任务数 < 核心线程数。
//任务数 a = 4 , 活跃线程数4 , 任务数 < 核心线程数。
//任务数 a = 5 , 活跃线程数5 , 任务数 = 核心线程数。
//任务数 a = 6 , 活跃线程数5 , 任务数 < 核心线程数5 + 队列长度2 。
//任务数 a = 7 , 活跃线程数5 , 任务数 = 核心线程数5 + 队列长度2 。

//任务数 a = 8 , 活跃线程数6 , 任务数 < 最大线程数8 + 队列长度2 。活跃线程数是在核心线程数5的基础上,加1个活跃线程。
//任务数 a = 9 , 活跃线程数7 , 任务数 < 最大线程数8 + 队列长度2 。活跃线程数是在核心线程数5的基础上,加2个活跃线程。
//任务数 a = 10 , 活跃线程数8 , 任务数 = 最大线程数8 + 队列长度2 。活跃线程数是在核心线程数5的基础上,加3个活跃线程。

//任务数 a = 11 , 活跃线程数8 , 任务数 > 最大线程数8 + 队列长度2 。抛出异常RejectedExecutionException

总结:
随着任务数量的增加,会增加活跃的线程数。

当活跃的线程数 = 核心线程数,此时不再增加活跃线程数,而是往任务队列里堆积。

当任务队列堆满了,随着任务数量的增加,会在核心线程数的基础上加开线程。

直到活跃线程数 = 最大线程数,就不能增加线程了。

如果此时任务还在增加,则: 任务数11 > 最大线程数8 + 队列长度2 ,抛出异常RejectedExecutionException,拒绝任务。

execute和submit的区别与联系
execute和submit都属于线程池的方法,execute只能提交Runnable类型的任务,而submit既能提交Runnable类型任务也能提交Callable类型任务。

execute会直接抛出任务执行时的异常,submit会吃掉异常,可通过Future的get方法将任务执行时的异常重新抛出。

execute所属顶层接口是Executor,submit所属顶层接口是ExecutorService,实现类ThreadPoolExecutor重写了execute方法,抽象类AbstractExecutorService重写了submit方法。

相关参数allowCoreThreadTimeOut:

是否允许核心线程超时退出。
如果该值为false,且poolSize<=corePoolSize,线程池都会保证这些核心线程处于存活状态,不会超时退出。
如果为true,则不论poolSize的大小,都允许超时退出。
如果poolSize>corePoolSize,则该参数不论true还是false,都允许超时退出。
相关判断如下:
(poolSize > corePoolSize || allowCoreThreadTimeOut)

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值