文章目录

  • 概述
  • Executor框架结构
  • Executor框架的使用示意图
  • `ThreadPoolExecutor`类介绍
    • `ThreadPoolExecutor` 类分析
    • 推荐使用 `ThreadPoolExecutor` 构造函数创建线程池
  • `ThreadPoolExecutor`使用示例
    • 示例代码1:Runnable+ThreadPoolExecutor
    • 示例代码2:Callable+ThreadPoolExecutor
    • 线程池原理分析
    • 几个常见的对比
      • `Runnable`VS`Callable`
      • `execute()`VS`submit()`
      • `shutdown()`VS`shutdownNow()`
      • `isTerminated()`VS`isShutdown()`
  • 几种常见线程池详解
    • FixedThreadPool
      • 概述
      • 执行任务过程介绍
      • 为什么不推荐使用FixedThreadPool?
    • SingleThreadExecutor详情
      • 概述
      • 执行任务过程介绍
      • 为什么不推荐使用SingleThreadExecutor?
    • CachedThreadPool详解
      • 概述
      • 执行任务过程介绍
      • 为什么不推荐使用CachedThreadPool?
  • 线程池大小确定

概述

Executor 框架是 Java5 之后引进的,在 Java 5 之后,通过 Executor 来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。

this 逃逸是指在构造函数返回之前其他线程就持有该对象的引用。调用尚未构造完全的对象的方法可能引发令人疑惑的错误。

在 HotSpot VM 的线程模型中,Java线程(java.lang.Thread)被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程;当该Java线程终止时,这个操作系统线程也被回收。操作系统会调度所有线程并将它们分配给可用的CPU。

在上层,Java多线程程序通常把应用分解为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定的数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。这两级的调度模型的示意图如下所示:

从上图可以看出,应用程序通过 Executor 框架控制上层的调度;而下层的调度由操作系统内核控制,下层的调度不受应用程序的控制。

Executor 框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,Executor 框架让并发编程变得更加简单。

##使用线程池的好处

  • 线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。
  • 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
  • 池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。

Executor框架结构

  1. 任务(Runnable/ Callable)

执行任务需要实现的 Runnable接口 或 Callable接口。Runnable 接口或 Callable 接口 实现类都可以被 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。

  1. 任务的执行(Executor)

如上图(任务的执行相关接口)所示,包括任务执行机制的核心接口 Executor ,以及继承自 Executor 接口的 ExecutorService 接口。ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 这两个关键类实现了 ExecutorService 接口。

ThreadPoolExecutor 类描述:

//AbstractExecutorService实现了ExecutorService接口
public class ThreadPoolExecutor extends AbstractExecutorService

ScheduledThreadPoolExecutor 类描述:

//ScheduledExecutorService实现了ExecutorService接口
public class ScheduledThreadPoolExecutorextends ThreadPoolExecutorimplements ScheduledExecutorService
  1. 异步计算的结果(Future)

    Future 接口以及 Future 接口的实现类 FutureTask 类都可以代表异步计算的结果。

    当我们把 Runnable接口 或 Callable 接口 的实现类提交给 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。(调用 submit() 方法时会返回一个 FutureTask 对象)

Executor框架的使用示意图

  1. 主线程首先要创建实现 Runnable 或者 Callable 接口的任务对象
  2. 把创建完成的实现 Runnable/Callable接口的 对象直接交给 ExecutorService 执行: ExecutorService.execute(Runnable command))或者也可以把 Runnable 对象或Callable 对象提交给 ExecutorService 执行(ExecutorService.submit(Runnable task)ExecutorService.submit(Callable task)
  3. 如果执行 ExecutorService.submit(…)ExecutorService 将返回一个实现Future接口的对象(刚才提到过了执行 execute()方法和 submit()方法的区别,submit()会返回一个 FutureTask 对象)。由于 FutureTask 实现了 Runnable,我们也可以创建 FutureTask,然后直接交给 ExecutorService 执行
  4. 最后,主线程可以执行 FutureTask.get()方法来等待任务执行完成。主线程也可以执行 FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。

ThreadPoolExecutor类介绍

线程池实现类 ThreadPoolExecutorExecutor 框架最核心的类。

ThreadPoolExecutor 类分析

ThreadPoolExecutor 类中提供的四个构造方法。来看最长的那个,其余三个都是在这个构造方法的基础上产生(其他几个构造方法说白点都是给定某些默认参数的构造方法比如默认制定拒绝策略是什么)

/*** 用给定的初始参数创建一个新的ThreadPoolExecutor。*/public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量int maximumPoolSize,//线程池的最大线程数long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间TimeUnit unit,//时间单位BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)thrownew IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)thrownew NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}

ThreadPoolExecutor 3 个最重要的参数:

  • corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。
  • maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
  • workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。

ThreadPoolExecutor其他常见参数:

  1. keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
  2. unit : keepAliveTime 参数的时间单位。
  3. threadFactory :executor 创建新线程的时候会用到。
  4. handler :饱和策略。关于饱和策略下面单独介绍一下。

下面可以加深对线程池中各个参数的相互关系的理解

ThreadPoolExecutor 饱和策略定义:

如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,ThreadPoolTaskExecutor 定义一些策略:

  • ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。
  • ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
  • ThreadPoolExecutor.DiscardPolicy 不处理新任务,直接丢弃掉。
  • ThreadPoolExecutor.DiscardOldestPolicy 此策略将丢弃最早的未处理的任务请求。

Spring 通过 ThreadPoolTaskExecutor 或者我们直接通过 ThreadPoolExecutor 的构造函数创建线程池的时候,当我们不指定 RejectedExecutionHandler 饱和策略的话来配置线程池的时候默认使用的是 ThreadPoolExecutor.AbortPolicy。在默认情况下,ThreadPoolExecutor 将抛出 RejectedExecutionException 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。对于可伸缩的应用程序,建议使用 ThreadPoolExecutor.CallerRunsPolicy。当最大池被填满时,此策略为我们提供可伸缩队列。

推荐使用 ThreadPoolExecutor 构造函数创建线程池

《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

Executors 返回线程池对象的弊端如下:

  • FixedThreadPoolSingleThreadExecutor :允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。
  • CachedThreadPool 和 ScheduledThreadPool :允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。

方式一:通过ThreadPoolExecutor构造函数实现(推荐使用)

方式二:通过 Executor 框架的工具类 Executors 来实现我们可以创建三种类型的 ThreadPoolExecutor:

  • FixedThreadPool
  • SingleThreadExecutor
  • CachedThreadPool

ThreadPoolExecutor使用示例

示例代码1:Runnable+ThreadPoolExecutor

首先创建一个 Runnable 接口的实现类(当然也可以是 Callable 接口)

import java.util.Date;/*** 这是一个简单的Runnable类,需要大约5秒钟来执行其任务。*/
public class MyRunnable implements Runnable {private String command;public MyRunnable(String s) {this.command = s;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());processCommand();System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());}private void processCommand() {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}@Overridepublic String toString() {returnthis.command;}
}

使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;publicclass ThreadPoolExecutorDemo {privatestaticfinalint CORE_POOL_SIZE = 5;privatestaticfinalint MAX_POOL_SIZE = 10;privatestaticfinalint QUEUE_CAPACITY = 100;privatestaticfinal Long KEEP_ALIVE_TIME = 1L;public static void main(String[] args) {//使用阿里巴巴推荐的创建线程池的方式//通过ThreadPoolExecutor构造函数自定义参数创建ThreadPoolExecutor executor = new ThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,new ArrayBlockingQueue<>(QUEUE_CAPACITY),new ThreadPoolExecutor.CallerRunsPolicy());for (int i = 0; i < 10; i++) {//创建WorkerThread对象(WorkerThread类实现了Runnable 接口)Runnable worker = new MyRunnable("" + i);//执行Runnableexecutor.execute(worker);}//终止线程池executor.shutdown();while (!executor.isTerminated()) {}System.out.println("Finished all threads");}
}
pool-1-thread-1 Start. Time = Wed May 06 23:20:09 CST 2020
pool-1-thread-4 Start. Time = Wed May 06 23:20:09 CST 2020
pool-1-thread-2 Start. Time = Wed May 06 23:20:09 CST 2020
pool-1-thread-5 Start. Time = Wed May 06 23:20:09 CST 2020
pool-1-thread-3 Start. Time = Wed May 06 23:20:09 CST 2020
pool-1-thread-4 End. Time = Wed May 06 23:20:14 CST 2020
pool-1-thread-1 End. Time = Wed May 06 23:20:14 CST 2020
pool-1-thread-3 End. Time = Wed May 06 23:20:14 CST 2020
pool-1-thread-5 End. Time = Wed May 06 23:20:14 CST 2020
pool-1-thread-2 End. Time = Wed May 06 23:20:14 CST 2020
pool-1-thread-5 Start. Time = Wed May 06 23:20:14 CST 2020
pool-1-thread-2 Start. Time = Wed May 06 23:20:14 CST 2020
pool-1-thread-3 Start. Time = Wed May 06 23:20:14 CST 2020
pool-1-thread-1 Start. Time = Wed May 06 23:20:14 CST 2020
pool-1-thread-4 Start. Time = Wed May 06 23:20:14 CST 2020
pool-1-thread-5 End. Time = Wed May 06 23:20:19 CST 2020
pool-1-thread-2 End. Time = Wed May 06 23:20:19 CST 2020
pool-1-thread-1 End. Time = Wed May 06 23:20:19 CST 2020
pool-1-thread-4 End. Time = Wed May 06 23:20:19 CST 2020
pool-1-thread-3 End. Time = Wed May 06 23:20:19 CST 2020

在以上代码中,指定了线程池的相关参数:

  1. corePoolSize: 核心线程数为 5。
  2. maximumPoolSize :最大线程数 10
  3. keepAliveTime : 等待时间为 1L。
  4. unit: 等待时间的单位为 TimeUnit.SECONDS。
  5. workQueue:任务队列为 ArrayBlockingQueue,并且容量为 100;
  6. handler:饱和策略为 CallerRunsPolicy

示例代码2:Callable+ThreadPoolExecutor

import java.util.concurrent.Callable;public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {Thread.sleep(1000);//返回执行当前 Callable 的线程名字return Thread.currentThread().getName();}
}
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;publicclass CallableDemo {privatestaticfinalint CORE_POOL_SIZE = 5;privatestaticfinalint MAX_POOL_SIZE = 10;privatestaticfinalint QUEUE_CAPACITY = 100;privatestaticfinal Long KEEP_ALIVE_TIME = 1L;public static void main(String[] args) {//使用阿里巴巴推荐的创建线程池的方式//通过ThreadPoolExecutor构造函数自定义参数创建ThreadPoolExecutor executor = new ThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,new ArrayBlockingQueue<>(QUEUE_CAPACITY),new ThreadPoolExecutor.CallerRunsPolicy());List<Future<String>> futureList = new ArrayList<>();Callable<String> callable = new MyCallable();for (int i = 0; i < 10; i++) {//提交任务到线程池Future<String> future = executor.submit(callable);//将返回值 future 添加到 list,我们可以通过 future 获得 执行 Callable 得到的返回值futureList.add(future);}for (Future<String> fut : futureList) {try {System.out.println(new Date() + "::" + fut.get());} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}//关闭线程池executor.shutdown();}
}

线程池原理分析

通过以上代码输出结果可以看出:线程池每次会同时执行 5 个任务,这 5 个任务执行完之后,剩余的 5 个任务才会被执行。

为了搞懂线程池的原理,我们需要首先分析一下 execute方法。在 以上中的 Demo 中我们使用 executor.execute(worker)来提交一个任务到线程池中去,这个方法非常重要,下面我们来看看它的源码:

// 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)privatefinal AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));private static int workerCountOf(int c) {return c & CAPACITY;}privatefinal BlockingQueue<Runnable> workQueue;public void execute(Runnable command) {// 如果任务为null,则抛出异常。if (command == null)thrownew NullPointerException();// ctl 中保存的线程池当前的一些状态信息int c = ctl.get();//  下面会涉及到 3 步 操作// 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize// 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}// 2.如果当前之行的任务数量大于等于 corePoolSize 的时候就会走到这里// 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态才会被并且队列可以加入任务,该任务才会被加入进去if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();// 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。if (!isRunning(recheck) && remove(command))reject(command);// 如果当前线程池为空就新创建一个线程并执行。elseif (workerCountOf(recheck) == 0)addWorker(null, false);}//3. 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。//如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。elseif (!addWorker(command, false))reject(command);}

以下为线程池的实现原理:

在代码中模拟了 10 个任务,我们配置的核心线程数为 5 、等待队列容量为 100 ,所以每次只可能存在 5 个任务同时执行,剩下的 5 个任务会被放到等待队列中去。当前的 5 个任务之行完成后,才会之行剩下的 5 个任务。

几个常见的对比

RunnableVSCallable

Runnable自 Java 1.0 以来一直存在,但Callable仅在 Java 1.5 中引入,目的就是为了来处理Runnable不支持的用例。Runnable 接口不会返回结果或抛出检查异常,但是**Callable 接口**可以。所以,如果任务不需要返回结果或抛出异常推荐使用 Runnable 接口,这样代码看起来会更加简洁。

工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换。(Executors.callable(Runnable task)或 Executors.callable(Runnable task,Object resule))。

execute()VSsubmit()

  1. execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
  2. submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功,并且可以通过 Futureget()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

shutdown()VSshutdownNow()

  • shutdown() :关闭线程池,线程池的状态变为 SHUTDOWN。线程池不再接受新任务了,但是队列里的任务得执行完毕。
  • shutdownNow() :关闭线程池,线程的状态变为 STOP。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。

isTerminated()VSisShutdown()

  • isShutDown 当调用 shutdown() 方法后返回为 true。
  • isTerminated 当调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true

几种常见线程池详解

FixedThreadPool

概述

FixedThreadPool 被称为可重用固定线程数的线程池。通过 Executors 类中的相关源代码来看一下相关实现:

/*** 创建一个可重用固定数量线程的线程池*/public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {returnnew ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory);}

外还有一个 FixedThreadPool 的实现方法,和上面的类似

public static ExecutorService newFixedThreadPool(int nThreads) {returnnew ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}

从上面源代码可以看出新创建的 FixedThreadPoolcorePoolSizemaximumPoolSize 都被设置为 nThreads,这个 nThreads 参数是我们使用的时候自己传递的。

执行任务过程介绍

FixedThreadPoolexecute() 方法运行示意图

上图说明:

  1. 如果当前运行的线程数小于 corePoolSize, 如果再来新任务的话,就创建新的线程来执行任务;
  2. 当前运行的线程数等于 corePoolSize 后, 如果再来新任务的话,会将任务加入 LinkedBlockingQueue
  3. 线程池中的线程执行完 手头的任务后,会在循环中反复从 LinkedBlockingQueue 中获取任务来执行

为什么不推荐使用FixedThreadPool?

FixedThreadPool 使用无界队列 LinkedBlockingQueue(队列的容量为 Intger.MAX_VALUE)作为线程池的工作队列会对线程池带来如下影响 :

  1. 当线程池中的线程数达到 corePoolSize 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize;
  2. 由于使用无界队列时 maximumPoolSize 将是一个无效参数,因为不可能存在任务队列满的情况。所以,通过创建 FixedThreadPool的源码可以看出创建的 FixedThreadPoolcorePoolSizemaximumPoolSize 被设置为同一个值。
  3. 由于 1 和 2,使用无界队列时 keepAliveTime 将是一个无效参数;
  4. 运行中的 FixedThreadPool(未执行 shutdown()shutdownNow())不会拒绝任务,在任务比较多的时候会导致 OOM(内存溢出)。

SingleThreadExecutor详情

概述

SingleThreadExecutor 是只有一个线程的线程池。下面看看SingleThreadExecutor 的实现:

/***返回只有一个线程的线程池*/public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {returnnew FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory));}public static ExecutorService newSingleThreadExecutor() {returnnew FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}

从上面源代码可以看出新创建的 SingleThreadExecutorcorePoolSizemaximumPoolSize 都被设置为 1.其他参数和 FixedThreadPool 相同。

执行任务过程介绍

SingleThreadExecutor 的运行示意图

上图说明;

  1. 如果当前运行的线程数少于 corePoolSize,则创建一个新的线程执行任务;
  2. 当前线程池中有一个运行的线程后,将任务加入 LinkedBlockingQueue
  3. 线程执行完当前的任务后,会在循环中反复从LinkedBlockingQueue 中获取任务来执行

为什么不推荐使用SingleThreadExecutor?

SingleThreadExecutor 使用无界队列 LinkedBlockingQueue 作为线程池的工作队列(队列的容量为 Intger.MAX_VALUE)。SingleThreadExecutor 使用无界队列作为线程池的工作队列会对线程池带来的影响与 FixedThreadPool 相同。说简单点就是可能会导致 OOM

CachedThreadPool详解

概述

CachedThreadPool 是一个会根据需要创建新线程的线程池。下面通过源码来看看 CachedThreadPool 的实现:

/*** 创建一个线程池,根据需要创建新线程,但会在先前构建的线程可用时重用它。*/public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {returnnew ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),threadFactory);}public static ExecutorService newCachedThreadPool() {returnnew ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}

CachedThreadPoolcorePoolSize 被设置为空(0),maximumPoolSize被设置为 Integer.MAX.VALUE,即它是无界的,这也就意味着如果主线程提交任务的速度高于 maximumPool 中线程处理任务的速度时,CachedThreadPool 会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源。

执行任务过程介绍

CachedThreadPool 的 execute()方法的执行示意图

上图说明:

  1. 首先执行 SynchronousQueue.offer(Runnable task) 提交任务到任务队列。如果当前 maximumPool 中有闲线程正在执行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行 offer 操作与空闲线程执行的 poll 操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成,否则执行下面的步骤 2;
  2. 当初始 maximumPool 为空,或者 maximumPool 中没有空闲线程时,将没有线程执行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下,步骤 1 将失败,此时 CachedThreadPool 会创建新线程执行任务,execute 方法执行完成

为什么不推荐使用CachedThreadPool?

CachedThreadPool允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM

线程池大小确定

线程池数量的确定一直是困扰着程序员的一个难题,大部分在设定线程池大小的时候就是随心而定。并没有考虑过这样大小的配置是否会带来什么问题

首先,可以肯定的一点是线程池大小设置过大或者过小都会有问题。合适的才是最好,貌似在 95 % 的场景下都是合适的。

  • 如果我们设置的线程池数量太小的话,如果同一时间有大量任务/请求需要处理,可能会导致大量的请求/任务在任务队列中排队等待执行,甚至会出现任务队列满了之后任务/请求无法处理的情况,或者大量任务堆积在任务队列导致 OOM。这样很明显是有问题的!CPU 根本没有得到充分利用
  • 如果我们设置线程数量太大,大量线程可能会同时在争取 CPU 资源,这样会导致大量的上下文切换,从而增加线程的执行时间,影响了整体执行效率

上下文切换:

多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换

上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。

Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。

有一个简单并且适用面比较广的公式:

  • CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
  • I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。

Java基础篇:Executor框架相关推荐

  1. Java基础系列--Executor框架(一)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/8393618.html 一.Executor框架介绍 Executor框架是JDK1.5之后 ...

  2. Java 并发编程——Executor框架和线程池原理

    Java 并发编程系列文章 Java 并发基础--线程安全性 Java 并发编程--Callable+Future+FutureTask java 并发编程--Thread 源码重新学习 java并发 ...

  3. Java SSM篇2——框架的基本认识

    Java SSM篇2--框架的基本认识 1.什么是框架 框架就是一套规范,既然是规范,你使用这个框架就要遵守这个框架所规定的约束 框架可以理解为半成品软件,框架做好以后,接下来在它基础上进行开发 2. ...

  4. Java基础篇--设计模式

    目录 前言 设计模式 创建型模式 单例模式 工厂方法模式 抽象工厂模式 建造者模式 原型模式 结构型模式 适配器模式 桥接模式 组合模式 装饰模式 外观模式 亨元模式 代理模式 行为型模式: 访问者模 ...

  5. 《Java 后端面试经》Java 基础篇

    <Java 后端面试经>专栏文章索引: <Java 后端面试经>Java 基础篇 <Java 后端面试经>Java EE 篇 <Java 后端面试经>数 ...

  6. Java基础篇 学习笔记

    List item Java基础篇 学习笔记 java基础篇 第1章 计算机.程序和java概述 学习笔记 1.1什么是计算机 简单来说:计算机就是 ' 存储 ' 和 ' 处理 ' 数据的电子设备. ...

  7. Thinking in java基础之集合框架

    Thinking in java基础之集合框架 大家都知道我的习惯,先上图说话. 集合简介(容器) 把具有相同性质的一类东西,汇聚成一个整体,就可以称为集合,例如这里有20个苹果,我们把每一个苹果当成 ...

  8. 你所需要的java基础篇深入解析大汇总

    java基础篇深入解析大总结 java基础(一) 深入解析基本类型 java基础(二) 自增自减与贪心规则 java基础(三) 加强型for循环与Iterator java基础(四) java运算顺序 ...

  9. Java基础篇4——数组

    Java基础篇4--数组 1.数组的概念 当需要在Java程序中记录单个数据内容时,则声明一个变量即可 当需要在Java程序中记录多个类型相同的数据内容时,则声明一个一维数 组即可,一维数组本质上就是 ...

  10. Java基础篇3——流程控制

    Java基础篇3--流程控制 1.顺序结构 正常代码的流程即是顺序流程 2.分支结构 2.1.if-else分支 if(条件表达式) {语句块1; } if(条件表达式) {语句块1; } else ...

最新文章

  1. 甲方爸爸,大概你要的是代码生成器吧?
  2. 使用Clion开发C语言程序,环境安装配置,保姆级教程
  3. java nio 阻塞_Java NIO和传统阻塞IO
  4. 进阶篇-安卓系统:5.安卓手机位置传感器
  5. 1-9其他数据库注入
  6. 提升沟通效率52%  阿里政务钉钉助力政府数字化转型
  7. 操作系统之计算机系统概述:5、中断和异常
  8. MySQL 修改字段
  9. 2018年终盘点:“年度爆款”的区块链真的结束了吗?
  10. php可以在dw中吗,php新手求助,为什么在DW中设计里能看到运行php程序的结果,但…...
  11. Django 缓存、序列化、信号
  12. 5阶无向完全图_离散数学图论答案
  13. 大淘客php源码美化版,【完整包】大淘客cms升级源码v12221 大淘客二次开发源码 - 下载 - 搜珍网...
  14. Android一键转发图片多张图片到微信,朋友圈功能实现
  15. CodeForces Round#524B - Margarite and the best present (前缀和)
  16. 冰点还原安装了找不到图标,或许该这样!
  17. 李嘉诚80个人生经典语录
  18. python中abs和fabs的区别_Python - abs vs fabs
  19. 笨办法学python pdf 第三版_笨办法学python第三版
  20. Vue - 姓名案例

热门文章

  1. php 预订义变量,预约义变量 PHP
  2. mysql取最早和最晚时间_SQL Server 按日期选择最晚日期与最早日期对应的数的差值...
  3. git 修改仓库的描述_git简介、基本命令和仓库操作
  4. java体重指数计算器程序_育儿 - 低体重儿和巨大儿的危害
  5. JSP 自定义标签介绍
  6. 数据库为何不建议部署在Docker容器内
  7. 第1关:实现一个顺序存储的队列
  8. HDU3089(约瑟夫环问题)
  9. 关于学习Python的一点学习总结(31->继承及多态)
  10. 关于学习Python的一点学习总结(14->换一种方法创建字典->访问字典)