wuqiu-ai / daily-interviews

java面试之每日一练
0 stars 0 forks source link

[基础] 第13天 说说ThreadPoolExecutor的参数含义 2020-09-08 #79

Open wuqiu-ai opened 4 years ago

wuqiu-ai commented 4 years ago

说说ThreadPoolExecutor的参数含义?

wuqiu-ai commented 4 years ago

ThreadPoolExecutor的执行过程

11111

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    int c = ctl.get();
    //如果线程池的线程个数少于corePoolSize则创建新线程执行当前任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //如果线程个数大于corePoolSize或者创建线程失败,则将任务存放在阻塞队列workQueue中
    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);
}

execute 方法执行逻辑有这样几种情况: 如果当前运行的线程少于 corePoolSize,则会创建新的线程来执行新的任务;如果运行的线程个数等于或者大于 corePoolSize,则会将提交的任务存放到阻塞队列 workQueue 中;如果当前 workQueue 队列已满的话,则会创建新的线程来执行任务;如果线程个数已经超过了 maximumPoolSize,则会使用饱和策略 RejectedExecutionHandler 来进行处理。

ThreadPoolExecutor的构造函数

  1. corePoolSize:核心线程数
  2. maxminPoolSize:线程不够用时能够创建的最大线程数
  3. woreQueue:任务等待队列,可以使用ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue
  4. keepAliveTime:抢占的顺序不一定
  5. threaFactory:创建新线程的工厂,Executors.defaultThreadFactort()
  6. handle:线程池的饱和策略

    1)AborePolicy:直接抛弃异常,默认策略 2)CallerRunsPolicy:用调用者所在的线程来执行任务 3)DiscardOldestPolicy:丢弃队列中靠前的任务,并执行当前任务 4)DiscardPolicy:直接丢弃任务 5)实现RejectedExecutionHandler接口的自定义handler

线程池的状态

  1. Ruuning:能接受新提交的任务,并且也能处理阻塞队列的任务
  2. Shutdown:不在接受新提交的任务,但可以处理存量任务
  3. Stop:不在接受新提交的任务,也不处理存量任务
  4. Tidying:所有的任务都终止
  5. Terminated:terminated()方法执行完后进入该状态

关闭线程池,可以通过shutdown和shutdownNow这两个方法。它们的原理都是遍历线程池中所有的线程,然后依次中断线程。shutdown和shutdownNow还是有不一样的地方:

shutdownNow首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行和未执行任务的线程,并返回等待执行任务的列表;shutdown只是将线程池的状态设置为SHUTDOWN状态,然后中断所有没有正在执行任务的线程 可以看出 shutdown 方法会将正在执行的任务继续执行完,而 shutdownNow 会直接中断正在执行的任务。调用了这两个方法的任意一个,isShutdown方法都会返回 true,当所有的线程都关闭成功,才表示线程池成功关闭,这时调用isTerminated方法才会返回 true。

如何合理设置线程池大小

要想合理的配置线程池,就必须首先分析任务特性,可以从以下几个角度来进行分析:

任务的性质:CPU 密集型任务,IO 密集型任务和混合型任务。任务的优先级:高,中和低。任务的执行时间:长,中和短。任务的依赖性:是否依赖其他系统资源,如数据库连接。 任务性质不同的任务可以用不同规模的线程池分开处理。CPU 密集型任务配置尽可能少的线程数量,如配置Ncpu+1个线程的线程池。IO 密集型任务则由于需要等待 IO 操作,线程并不是一直在执行任务,则配置尽可能多的线程,如2xNcpu。混合型的任务,如果可以拆分,则将其拆分成一个 CPU 密集型任务和一个 IO 密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。我们可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的 CPU 个数。 优先级不同的任务可以使用优先级队列 PriorityBlockingQueue 来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。 执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。 依赖数据库连接池的任务,因为线程提交 SQL 后需要等待数据库返回结果,如果等待的时间越长 CPU 空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用 CPU。 并且,阻塞队列最好是使用有界队列,如果采用无界队列的话,一旦任务积压在阻塞队列中的话就会占用过多的内存资源,甚至会使得系统崩溃。