ThreadPoolExecutor线程池笔记

线程池


前言

线程池因为可以线程复用而在工作使用频繁,由此需掌握线程池的基本参数和大致工作流程

一、线程池参数

创建线程池时,需要指定核心线程数,最大线程数,空闲时间,时间类型,线程工厂,拒绝策略,任务队列
其中核心线程数和最大线程数的设定规则有两种,IO密集型还是cpu密集型
查mysql或者file处理都是IO密集型
对于cpu密集型,线程数最好等于cpu核心数,我们可以通过一下api获取

Runtime.getRuntime().availableProcessors()

为了以防万一,我们往往会多加一个线程最为替补,所以线程数最佳为cpu核心数+1
IO型由于大部分执行时间可能在IO的处理上,cpu并没有一直在运行,从而导致资源浪费,所以通常线程数最佳为2*cpu核心数。但是可能2倍的cpu核心数也不够执行,则可以用以下公式

线程数=cpu核心数*(1+线程等待时间/运行总时间)
可以用jvisualvm.exe(jdk的bin目录下有)来估计

线程池指定了核心线程的数量,但一开始线程池中不会有线程,而是随着任务的提交创建线程

线程池有五个状态运行中,关闭,停止,池空,终结

     *   RUNNING:  Accept new tasks and process queued tasks
     *   SHUTDOWN: Don't accept new tasks, but process queued tasks
     *   STOP:     Don't accept new tasks, don't process queued tasks,
     *             and interrupt in-progress tasks
     *   TIDYING:  All tasks have terminated, workerCount is zero,
     *             the thread transitioning to state TIDYING
     *             will run the terminated() hook method
     *   TERMINATED: terminated() has completed

    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

二、线程池工作流程

接下来根据几个问题来解释线程池工作的部分场景

1.当线程池中的线程有空闲时,新的任务加进来会创建新的线程,还是利用旧的线程?
这个问题需要分情况,观察源码execute方法可发现,第2个if中判断若线程池中已有的线程数小于指定的核心线程数,则无论是否有空闲线程,依旧创建新的线程。
第3个if中判断若当前线程数大于等于核心线程数,则提交的任务offer进入工作等待队列BlockingQueue
第4个if中判断当等待队列已满,加入不成功时,调用addWorker方法。这个方法会判断当前线程数是否大于设置的最大线程maximumPoolSize数,若小于则创建一个新线程去执行任务。由此可见线程池的任务是非公平的,可能后发起的任务先执行

 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

2.当前线程池线程个数大于核心线程数时,空闲的线程是否会销毁?
答案是会
这里需要看源码,在执行run,也就是runWorker方法时,它调用了getTask方法。可以看到最开始,会调用poll方法阻塞设定的空闲时间,若指定时间内无新任务提交,则timedOut = true,从而调用compareAndDecrementWorkerCount方法,通过cas让当前线程数-1,并返回null 销毁当前的线程

 private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值