概述

线程池的创建⽅法总共有 7 种(其中 6 种是通过 Executors 创建的, 1 种是通过ThreadPoolExecutor 创建的),但总体来说可分为 2 类:

  1. 通过 ThreadPoolExecutor 创建的线程池;
  2. 通过 Executors 创建的线程池(下面只说四种)。

7种创建方法
java中涉及到线程池的相关类均在jdk1.5开始的java.util.concurrent包中,涉及到的几个核心类及接口包括:Executor、Executors、ExecutorService、ThreadPoolExecutor、FutureTask、Callable、Runnable等。

Executor/ExecutorService

Executor,Executors,ExecutorService,ThreadExecutorPool之间的关系,博文

常使用的Executors实现创建线程池使用线程主要是用下面类图中提供的类,其线程池类图如下:

它包含了三个executor接口:

  1. Executor:运行新任务的简单接口
  2. ExecutorService:扩展了Executor,添加了用来管理执行器生命周期和任务生命周期的方法
  3. ScheduleExcutorService:扩展了ExecutorService,支持Future和定期执行任务

参考博文

Executors

是一个线程池工厂,提供了很多的工厂方法,创建了下面的线程池

// 创建单一线程的线程池
public static ExecutorService newSingleThreadExecutor();
// 创建固定数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads);
// 创建带缓存的线程池
public static ExecutorService newCachedThreadPool();
// 创建定时调度的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
// 创建流式(fork-join)线程池,创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】
public static ExecutorService newWorkStealingPool();
//创建⼀个单线程的可以执⾏延迟任务的线程池;
Executors.newSingleThreadScheduledExecutor

java通过Executors提供四种线程池:自动创建线程池的几种方式都封装在Executors工具类中

  1. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

线程池核心类-ThreadPoolExecutor

ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置;

这个用于手动创建线程池,供了好几个构造方法,最底层的构造方法只有下面这一个

/*corePoolSize:核心线程数,也是线程池中常驻的线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务maximumPoolSize:最大线程数,在核心线程数的基础上可能会额外增加一些非核心线程,需要注意:只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)keepAliveTime:非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,注意当corePoolSize=maxPoolSize时,keepAliveTime参数也就不起作用了(因为不存在非核心线程);unit:keepAliveTime的时间单位workQueue:用于保存任务的队列,等待队列,线程池中的线程数超过核心线程数时,任务将放在等待队列,它是一个BlockingQueue类型的对象可以为无界、有界、同步移交三种队列类型之一,当池子里的工作线程数大于corePoolSize时,这时新进来的任务会被放到队列中threadFactory:创建线程的工厂类,默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建handler:线程池无法继续接收任务队列已满且线程数达到maximunPoolSize)时的饱和策略取值有:AbortPolicy:中断抛出异常CallerRunsPolicy:默默丢弃任务,不进行任何通知DiscardOldestPolicy:丢弃掉在队列中存在时间最久的任务DiscardPolicy:让提交任务的线程去执行任务(对比前三种比较友好一丢丢)*/public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {……}

对于上面的这些参数,最关心的是workQueue、threadFactory和handler

等待队列-workQueue

等待队列是BlockingQueue类型的,理论上只要是它的子类,我们都可以用来作为等待队列。
下面是jdk内部自带一些阻塞队列

  1. ArrayBlockingQueue,队列是有界的,基于数组实现的阻塞队列
  2. LinkedBlockingQueue,队列可以有界,也可以无界。基于链表实现的阻塞队列
  3. SynchronousQueue,不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作将一直处于阻塞状态。该队列也
  4. Executors.newCachedThreadPool()的默认队列
  5. PriorityBlockingQueue,带优先级的无界阻塞队列

线程工厂-threadFactory

ThreadFactory是一个接口,只有一个方法

拒绝策略-handler

拒绝策略,就是当线程池满了、队列也满了的时候,我们对任务采取的措施

  1. AbortPolicy:中断抛出异常
  2. CallerRunsPolicy:默默丢弃任务,不进行任何通知
  3. DiscardOldestPolicy:丢弃掉在队列中存在时间最久的任务
  4. DiscardPolicy:让提交任务的线程去执行任务(对比前三种比较友好一丢丢)

四种策略各有优劣,比较常用的是DiscardPolicy,但是这种策略有一个弊端就是任务执行的轨迹不会被记录下来。所以,我们往往需要实现自定义的拒绝策略, 通过实现RejectedExecutionHandler接口的方式

FutureTask/Runnable

FutureTask一个可取消的异步计算,FutureTask 实现了Future的基本方法

FutureTask类中常用方法:博客参考2

  1. public boolean isCancelled() 如果此任务在正常完成之前取消,则返回 true 。
  2. public boolean isDone() 返回true如果任务已完成。
  3. public V get() 等待计算完成,然后检索其结果。
  4. public V get(long timeout, TimeUnit unit),如果需要等待最多在给定的时间计算完成,然后检索其结果(如果可用)。
  5. public boolean cancel(boolean mayInterruptIfRunning),尝试取消执行此任务。
  6. protected void set(V v):将此未来的结果设置为给定值,除非此未来已被设置或已被取消。

创建FutureTask的两种方法:博客参考1

  1. 使用Callable

     FutureTask<Boolean> future = new FutureTask<>(new Callable<Boolean>() {@Overridepublic Boolean call() throws Exception {return true;}});
  2. 直接new一个FutureTask
    //托管给线程池处理
    Future<?> futureResult = Executors.newCachedThreadPool().submit(future);
    //托管给单独线程处理
    //FutureTask继承了Runnable接口,
    //所以它可以通过new Thread()的方式进行运行,
    //再由future变量来检索结果值或者取消任务等操作,通过线程池托管的方式也可以适用。
    new Thread(future).start();

Callable

线程池

参考博文

newCachedThreadPool

特点:

  1. 线程池数量上限为:Integer.MaxValue(2147483647);
  2. 线程池默认空闲60S,超过60S会从线程池中移除
  3. 新来任务时,先检查是否有空闲线程可使用,若无,则创建一个新线程执行任务
  4. 任务存储在同步队列中。
  5. 适用场景:短时异步任务
    构造方法:
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}

newFixedThreadPool

特点:

  1. 用于创建一个可重用、固定线程数量的线程池;
  2. 当线程池中线程都处于运行中时,新来的线程会进入等待状态,直到线程池中出现一个空闲线程
  3. 当一个线程在任务中途退出、终止时,会有一个新的线程来替代它继续完成后面未完成的任务。
  4. 除非采用显式关闭的方法去关闭某个线程,否则线程会一直存在,不会释放资源。
  5. 任务存储在无界阻塞队列中
  6. 适用场景:长期任务

构造方法:

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

newScheduledThreadPool

特点:

  1. 任务存储在无界延迟队列中
  2. 适用场景:需要定期执行或延迟执行的任务

构造方法:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);
}public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());
}

newSingleThreadExecutor

特点:

  1. 创建一个单个Worker的线程;
  2. 线程会按照顺序依次执行
  3. 任务存储在无界阻塞队列中
  4. 适用场景:需要按照顺序执行的任务

构造方法:

public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}

常用方法:

public static void main(String args[])
{/** 执行定时任务newScheduledThreadPool*/ScheduledExecutorService service = Executors.newScheduledThreadPool(10);//方法1:5秒后开始执行,每隔一秒执行一次/*scheduleAtFixedRate方法,一共四个参数,分别是:1.需要执行的任务task、2.延迟执行时间t1、3.每次执行任务的时间间隔t2、4.时间间隔单位。含义是:在t1时间过后,以 1次/t2 的频率来不断执行 task。代码中,在5秒延迟后,以 1次/1秒的频率执行 打印当前时间的任务。*/service.scheduleAtFixedRate(new ExecutorsTest2(), 5, 1, TimeUnit.SECONDS);//方法2:5秒后开始执行,每次任务执行完后延迟3秒后,继续执行下一次/*scheduleWithFixedDelay也是四个参数,分别是:1.待执行的任务Task,2.延迟时间t1,3.每次任务执行完毕后延迟t2秒后执行下次任务,4.延迟时间单位。*/service.scheduleWithFixedDelay(new ExecutorsTest3(), 5, 3, TimeUnit.SECONDS);
}

配置线程池的参数

根据任务的特性来分析。

  1. 任务的性质:CPU密集型、IO密集型和混杂型
  2. 任务的优先级:高中低
  3. 任务执行的时间:长中短
  4. 任务的依赖性:是否依赖数据库或者其他系统资源

如果任务属于CPU密集型,那么我们可以将线程池数量设置成CPU的个数,以减少线程切换带来的开销。
如果任务属于IO密集型,我们可以将线程池数量设置得更多一些,比如CPU个数*2。

可以通过Runtime.getRuntime().availableProcessors()来获取CPU的个数

与Future/Callable联合使用

主线程需要知道子线程的运行结果,以便确定如何执行任务.JDK1.5以后就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
使用Callable步骤:参考博文

  1. 任务类实现Callable接口
  2. 创建线程池:ExecutorService es = Executors.newCachedThreadPool();
  3. 执行任务:chuju cj = new chuju();Future future = es.submit(cj);
  4. 获取子线程中任务的执行结果:future.get()

Future

FutureTask 类是 Future 接口的实现类,提供对异步任务的操作的具体实现。

FutureTask 类不仅仅实现了 Future 接口,而且实现了 Runnable 接口,或者更加准确地说,FutureTask 类实现 了 RunnableFuture 接口

参考博客

当我们执行某一耗时的任务时,我们可以另起一个线程异步去执行这个耗时的任务,同时我们可以干点其他事情。当事情干完后我们再根据future这个"单号"去提取耗时任务的执行结果即可。因此Future也是多线程中的一种应用模式

跟Future使用有关的JDK类主要有FutureTask和Callable两个

把任务提交给子线程去处理,主线程不用同步等待,当向线程池提交了一个Callable或Runnable任务时就会返回Future,用Future可以获取任务执行的返回结果

Future接口象征着异步执行任务的结果即执行一个耗时任务完全可以另起一个线程执行,然后此时我们可以去做其他事情,做完其他事情我们再调用Future.get()方法获取结果即可,此时若异步任务还没结束,此时会一直阻塞等待,直到异步任务执行完获取到结果。

Future的主要方法包括:

  1. get()方法:返回任务的执行结果,若任务还未执行完,则会一直阻塞直到完成为止,如果执行过程中发生异常,则抛出异常,但是主线程是感知不到并且不受影响的,除非调用get()方法进行获取结果则会抛出ExecutionException异常;

  2. get(long timeout, TimeUnit unit):在指定时间内返回任务的执行结果,超时未返回会抛出TimeoutException,这个时候需要显式的取消任务;

  3. cancel(boolean mayInterruptIfRunning):取消任务,boolean类型入参表示如果任务正在运行中是否强制中断;

  4. isDone():判断任务是否执行完毕,执行完毕不代表任务一定成功执行,比如任务执行失但也执行完毕、任务被中断了也执行完毕都会返回true,它仅仅表示一种状态说后面任务不会再执行了;

  5. isCancelled():判断任务是否被取消;

参考博文

参考2

Callable

Runnable是出自jdk1.0,Callable出自jdk1.5,所以Callable肯定对于前者有增强

Runnable的run方法与Callable的call方法进行对比:Callable的call方法有返回值并可以抛出异常,而run方法没有返回值

// Runnable
public abstract void run();// Callable
V call() throws Exception;

Callable的基本用法

通过FutureTask,交给线程池

通过FutureTask,交给线程池执行,阻塞等待结果返回

  1. 直接使用submit:callable的逻辑是在异步线程中处理的,主线程通过阻塞式接受异步线程返回的内容。简单来说,Callable里面执行的内容是在另一个线程中执行,但是这个线程是阻塞的,需要执行完,才能继续主线程的运行

        public static void main(String[] args) throws Exception {FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {public String call() throws InterruptedException {Thread.sleep(3000L);System.out.println("当前线程:" + Thread.currentThread().getName());return "hello world";}});ExecutorService executorService = Executors.newFixedThreadPool(1);System.out.println("开始时间戳为:" + System.currentTimeMillis());executorService.submit(futureTask);String result = futureTask.get();System.out.println("结束时间戳为:" + System.currentTimeMillis() + ",result = " + result);}
    结果打印:
    开始时间戳为:...17192
    当前线程:pool-1-thread-1
    结束时间戳为:...20202,result = hello world
    
  2. 直接使用FutureTask.run():和直接调用Runnable的方法一样,一直都是由主线程执行

        public static void main(String[] args) throws Exception {FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {public String call() throws InterruptedException {Thread.sleep(3000L);System.out.println("当前线程:" + Thread.currentThread().getName());return "hello world";}});System.out.println("开始时间戳为:" + System.currentTimeMillis());futureTask.run();String result = futureTask.get();System.out.println("结束时间戳为:" + System.currentTimeMillis() + ",result = " + result);}
    结果打印:
    开始时间戳为:...59794
    当前线程:main
    结束时间戳为:...62796,result = hello world
    

通过线程池执行,返回Future对象

该方法与上面的方法中方式1的执行效果一样,就是返回结果是Future,实际上也是FutureTask

    public static void main(String[] args) throws Exception {ExecutorService executorService = Executors.newFixedThreadPool(1);System.out.println("开始时间戳为:" + System.currentTimeMillis());Future<String> future = executorService.submit(new Callable<String>() {public String call() throws InterruptedException {Thread.sleep(3000L);System.out.println("当前线程:" + Thread.currentThread().getName());return "hello world";}});String result = future.get();System.out.println("结束时间戳为:" + System.currentTimeMillis() + ",result = " + result);}
结果打印:
开始时间戳为:...55569
当前线程:pool-1-thread-1
结束时间戳为:...58583,result = hello world

原理分析

线程池的submit方法

    // FutureTask传入方式调用的submit方法public Future<?> submit(Runnable task) {if (task == null) throw new NullPointerException();RunnableFuture<Void> ftask = newTaskFor(task, null);execute(ftask);return ftask;}// Callable传入方式调用的submit方法public <T> Future<T> submit(Callable<T> task) {if (task == null) throw new NullPointerException();RunnableFuture<T> ftask = newTaskFor(task);execute(ftask);return ftask;}

future.get方法阻塞等待异步线程执行结果

FutureTask内部维护了任务进行的状态,当异步任务完成时,会将状态码设置为已完成,如果发生异常,会将状态码设置成对应的异常状态码。

    public V get() throws InterruptedException, ExecutionException {int s = state;if (s <= COMPLETING)// 如果状态还在进行中,或者刚创建,就阻塞等待s = awaitDone(false, 0L);// 调用Report,返回结果或者抛出异常return report(s);}private V report(int s) throws ExecutionException {Object x = outcome;if (s == NORMAL)// 状态正常,返回结果return (V)x;if (s >= CANCELLED)// 状态取消,抛出取消异常throw new CancellationException();// 抛出程序执行异常throw new ExecutionException((Throwable)x);}












总结

参考博文:

  1. 博文1
  2. 博文2
  3. 博文3

线程池的使用(结合Future/Callable使用)相关推荐

  1. terminated 线程_Java【多线程系列】JUC线程池—2. 原理(二)、Callable和Future

    在"Java多线程系列--"基础篇"01之 基本概念"中,我们介绍过,线程有5种状态:新建状态,就绪状态,运行状态,阻塞状态,死亡状态.线程池也有5种状态:然而 ...

  2. Java并发编程之线程池中的Future

    线程池中的Future: 线程池的典型使用场景 ExecutorService executorService = Executors.newFixedThreadPool(10);//此处Task为 ...

  3. 创建线程的四种方式(Thread、Runnable、线程池、Callable)

    目录 一.直接继承Thread类,然后重写run方法 二.实现Runnable接口 三.线程池创建 四.实现Callable接口 创建线程有四种方式:1.继承Thread类   2.实现Runnabl ...

  4. 谈谈java的线程池(创建、机制)

    目录 Executors创建线程池默认方法 自定义线程池 Executors创建线程池默认方法 newFixedThreadPool()方法,该方法返回一个固定数量的线程池,该方法的线程数始终不变,当 ...

  5. java线程的创建线程_多线程(Thread、线程创建、线程池)

    第1章 多线程 1.1 多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念. 进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序, ...

  6. Java核心(二)深入理解线程池ThreadPool

    本文你将获得以下信息: 线程池源码解读 线程池执行流程分析 带返回值的线程池实现 延迟线程池实现 为了方便读者理解,本文会由浅入深,先从线程池的使用开始再延伸到源码解读和源码分析等高级内容,读者可根据 ...

  7. Java多线程学习(八)线程池与Executor 框架

    历史优质文章推荐: Java并发编程指南专栏 分布式系统的经典基础理论 可能是最漂亮的Spring事务管理详解 面试中关于Java虚拟机(jvm)的问题看这篇就够了 目录: [TOC] 本节思维导图: ...

  8. 线程池很难么?带你从头到尾捋一遍,不信你听不懂!

    点击关注公众号,实用技术文章及时了解 来源:blog.csdn.net/qq_43061290/article/ details/106911277 目标 [理解]线程池基本概念 [理解]线程池工作原 ...

  9. Java提高班(二)深入理解线程池ThreadPool

    本文你将获得以下信息: 线程池源码解读 线程池执行流程分析 带返回值的线程池实现 延迟线程池实现 为了方便读者理解,本文会由浅入深,先从线程池的使用开始再延伸到源码解读和源码分析等高级内容,读者可根据 ...

最新文章

  1. 20150920 DNS服务
  2. 【导纳分析】基于FPGA的导纳分析仪的verilog设计
  3. 随机梯度下降的实现细节
  4. CSDN 统一标签设计 征求反馈
  5. 关于虚拟机linux密码的那点事
  6. Get IAT Table
  7. [Springboot]SpringCache + Redis实现数据缓存
  8. 2018 Wannafly summer camp Day8--区间权值
  9. 决策树算法的应用python实现_决策树ID3和C4.5算法Python实现源码
  10. 6U VPX SRIO交换板
  11. 实现.pb模型和.pbtxt模型之间的转换 python
  12. 分享一个无意间发现的躺赚网络创业小项目!
  13. 其他技术 网易云音乐Mp3,通过网易官方搜索引擎获取mp3外链
  14. 解决 QGC地面站 ( QGroundControl )停止工作-由于win7 ghost精简缺少语音包
  15. AD与AAD区别和联系
  16. 计算机论文折线图,干货 | 画论文折线图、曲线图?几个代码模板轻松搞定!
  17. 打开计算机无法最小化,软件一打开就最小化到任务栏怎么办_电脑打开程序它就最小化的解决方法...
  18. matlab求解振动学问题,振动力学基础与MATLAB应用
  19. 工业环境中的LED照明降低成本,提高安全性
  20. Visual C#中用WMI获取远程计算机信息

热门文章

  1. 主磁盘分区,扩展磁盘分区,逻辑驱动器
  2. 带GPS的SLAM数据集汇总
  3. windows 系统下nmap扫描报错的解决方法
  4. 11月中旬鸿蒙系统,鸿蒙系统开源联姻国产家电 新品最快11月开卖
  5. MATLAB-常微分方程求解
  6. 第六届蓝桥杯大赛省赛C语言B组-填空题-星系炸弹(Java实现)
  7. 三国杀显示服务器登录错误,三国杀登录超时怎么办?登陆失败解决方法[多图]...
  8. F. DS图—图非0面积
  9. 20X29 FCPX插件50种可爱流行手绘图形MG元素包 Hand Painted
  10. 三菱PLC以太网模块FX3U-ENET-L的使用方法