1.为什么要用线程池

一些任务适合使用单独的线程去执行,而线程作为一项比较重的资源如果频繁创建对系统资源消耗较大。使用线程池,将线程重复使用,节省频繁创建线程的开销。
创建一个线程需要调用操作系统的API,然后操作系统要为线程分配一系列的资源,这个成本就很高了。所以应该避免线程的频繁创建和销毁。

2.线程池的定义

在Java中表示线程池的类为ThreadPoolExecutor。定义时最多需要设置7个参数。
1.核心线程数,线程池最少维护的线程个数

2.最大线程数,线程池最多创建的线程个数。当核心线程都处于忙碌状态,且阻塞队列已满时,继续创建线程,但不能超过最大线程数。

3和4 线程活跃时间,当线程超过该时间空闲时销毁线程,当然要保留核心线程数个线程

5 阻塞队列,任务提交时,当无空闲线程且线程个数到达核心线程数,则先将任务保存到该队列中。为了避免OOM阻塞队列需要设置一个上限,不建议使用Executors定义好的几种线程池就是因为它的阻塞队列是无界的。

6 线程工厂,定义创建线程的方式,比如指定线程名字

7 拒绝策略,当阻塞队列满了,这时再提交任务则触发决绝策略。默认提供了4种:抛出异常、静默处理、调用者线程自身处理、删除最早提交的任务。也可以自定义处理方式,比如使用另一个补偿线程池执行、放入MQ让其他消费者执行、放入ySQL启动一个线程遍历执行等。

    public static void func() {ThreadPoolExecutor executor = new ThreadPoolExecutor(2, //核心线程数5, //最大线程数10, //线程活跃时间,超过这个时间没有执行任务释放该线程TimeUnit.SECONDS, //活跃时间单位new ArrayBlockingQueue<>(1024), //阻塞队列,设置一个上限避免OOMnew ThreadFactory() {private final AtomicInteger threadId = new AtomicInteger(0);@Overridepublic Thread newThread(Runnable r) {return new Thread(r, "MyThreadPool-" + threadId.getAndIncrement());}}, //线程工厂,指定如何生成线程。这里给线程指定一个有意义的名字。(线程可以重名吗?)new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略,当所有线程都在忙碌,而阻塞队列到达上限时执行的操作。// 有4种已定义的:抛出异常、不执行静默处理、提交任务的线程自己执行、抛弃最早的提交的任务;// 也可以自定义处理方式,如放入消息队列进行补偿执行、放入数据库进行补偿(有个线程进行定时遍历)、使用专门的补偿线程池进行执行);}

3.提交任务

1.提交Runnable,不关心返回值、执行情况

ThreadPoolExecutor.execute(Runnable r)

此方法没有返回值,适用于任务执行结束不需要有处理结果的情况。且不能进行任务的取消。

2.提交Runnable,返回future,可以取消任务、等待任务结果

Future<?> future = ThreadPoolExecutor.submit(Runnable r)

返回一个future,提供了isDone()判断任务是否执行完;cancel(true)取消任务,参数表示如果任务已经在执行了是否发出Innterrupted中断;future.get()阻塞获取执行结果,当然这里提交的Runnable总是返回null,但可以作为等待任务结束的方法。

3.提交Callable,返回future,通过future拿到执行结果

Future<T> ThreadPoolExecutor.submit(Callable<T> c)

这个future的get()方法就可以获取到Callable的call方法的返回值。当然isDone、cancel等方法还是可以正常使用的

4.线程池的思想

1.池化资源思想
线程池是一种池,将线程创建后重复使用,是一种池化思想。池化思想在编程领域有广泛应用,比如对象池、连接池等。

2.生产者、消费者模型
和一般的池化资源不同,线程池并不是通过获取资源、使用资源、释放资源的步骤来使用的。而是一种生产者、消费者模型。提交任务是一种生产行为,线程池中的线程执行任务是消费行为。

5.线程池中线程个数应该如何设置

1.CPU密集型任务
一般设置为CPU核心数+1,因为任务大部分时间都在利用CPU,每个线程利用一个CPU。设置+1是为了偶尔的内存缺页等导致线程中断时有线程可以顶上。

2.IO密集型任务
任务大部分时间都在执行IO操作,比如调用外部接口等。线程数就可以设置的大于CPU核心数。一个理想公式为:(1 + I/O时间 / CPU时间) * 核心数。比如IO和Cpu之比为9:1,那么设置10个线程。当9个线程使用完CPU后,剩下一个线程正好开始使用CPU。每时每刻都有1个线程使用CPU,9个线程使用IO,两者的利用率都达到了100%。

6.线程池异常处理

对于使用execute(Runnable r)提交的任务,抛出异常时提交任务的线程不会得到通知。对于另外两种有future返回的submit()方法提交的任务,如果调用了get方法,发生异常时get会抛出异常。如果不调用get也感知不到异常。

推荐的方式:在任务中按需处理所有异常!

7.线程池监控指标

可以定时获取以下指标进行监控埋点:

//核心线程数,这个应该不会动态变化吧?
int corePoolSize = executor.getCorePoolSize();
//最大线程数
int maximumPoolSize = executor.getMaximumPoolSize();
//历史最大线程数
int largestPoolSize = executor.getLargestPoolSize();
//活跃线程数,正在执行任务的线程数?
int activeCount = executor.getActiveCount();
//当前线程池中线程总数
int poolSize = executor.getPoolSize();
//队列中任务个数
int size = executor.getQueue().size();

8.Executors

Java通过Executors类提供了4种默认的线程池,但是在实际情况中要谨慎使用,因为他们都使用了无界的任务队列,存在OOM的风险。

1.newFixedThreadPool
固定线程数的线程池。核心线程数=最大线程数,无界任务队列

ExecutorService executorService = Executors.newFixedThreadPool(10);# 实际定义public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}

2.newCachedThreadPool
核心线程数=0
最大线程数=最大整数值
空闲时间=60秒
无界任务队列

即线程最多空闲时间是60秒,有任务提交时如果还有线程则直接执行,否则直接创建新线程执行。

ExecutorService executorService1 = Executors.newCachedThreadPool();# 构造方法定义
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}

疑问:既然优先将任务放入阻塞队列。对于newCachedThreadPool线程池设置核心线程数为0,最大线程数为最大整数,阻塞队列貌似也无界,它是怎么运行的?
解答:关键在于他使用的阻塞队列SynchronousQueue,它并不是无界的,而是容量为1的同步队列。生产者线程必须等待队列中的任务被消费后才能继续放入任务。所以放入第二个任务时就会开始创建线程执行第一个任务。那么如果只放入一个线程,那是不是就永远不会被执行?实测发现并不会出现这种情况,而是立即执行了。
关于这个问题找到了下面这个解答:

由于ThreadPoolExecutor内部实现任务提交的时候调用的是工作队列(BlockingQueue接口的实现类)的非阻塞式入队列方法(offer方法),因此,在使用SynchronousQueue作为工作队列的前提下,客户端代码向线程池提交任务时,而线程池中又没有空闲的线程能够从SynchronousQueue队列实例中取一个任务,那么相应的offer方法调用就会失败(即任务没有被存入工作队列)。此时,ThreadPoolExecutor会新建一个新的工作者线程用于对这个入队列失败的任务进行处理(假设此时线程池的大小还未达到其最大线程池大小)。
作者:go4it
链接:https://www.jianshu.com/p/b7f7eb2bc778
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3.newSingleThreadExecutor
单线程线程池
核心线程数=最大线程数=1

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

4.newScheduledThreadPool
可以执行定时、延迟任务的线程池
可以执行两种任务,一种是定时周期性执行的;另一种是在固定延迟后执行一次。

这四种线程池知道是怎么回事就行。使用时要仔细判断是否适用自己的场景,特别是注意OOM问题。第四种定时执行在需要执行一些简单定时任务时还是可以使用的。

9.线程池执行流程


上图来自:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html

10.线程池终止

提供了shutdown()和shutdownNow()两个方法来终止线程池。
其中shutdown()方法调用后,线程池不再接收新提交的任务,会等待已提交的任务执行完成后终止线程池,包括正在执行的和阻塞队列中的任务。但shutdown()方法是立即返回的,不会等到线程池终止才返回。

shutdownNow()会立即终止线程池,正在执行的线程会收到Interrupted中断,调用了线程的interrupt()方法。队列中未执行的任务会返回一个Runnables列表,对于其中的Future可以进行cancel等处理。

11.线程池参数动态配置

Java线程池的知识相关推荐

  1. Java线程池ThreadPoolExecutor

    2019独角兽企业重金招聘Python工程师标准>>> 本文首发于个人微信公众号<andyqian>, 关注免费获取Java学习资料 前言 多线程一直是Java进阶的必修 ...

  2. java线程池拒绝策略_Java核心知识 多线程并发 线程池原理(二十三)

    线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后 启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕, 再从队列中取出任务来执行.他 ...

  3. Java线程池使用与原理

    线程池是什么? 我们可以利用java很容易创建一个新线程,同时操作系统创建一个线程也是一笔不小的开销.所以基于线程的复用,就提出了线程池的概念,我们使用线程池创建出若干个线程,执行完一个任务后,该线程 ...

  4. Java线程池实现原理及其在美团业务中的实践

    来自:美团技术团队 随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供的线程池ThreadPoolExecuto ...

  5. java线程池的使用例子,不愧是大佬

    京东Java研发岗一面(基础面,约1小时) 自我介绍,主要讲讲做了什么和擅长什么 springmvc和spring-boot区别 @Autowired的实现原理 Bean的默认作用范围是什么?其他的作 ...

  6. java线程池队列场景,Java面试题汇总

    01 并发宝典:面试专题 面试专题分为四个部分,分别如下 Synchronized 相关问题 可重入锁 ReentrantLock 及其他显式锁相关问题 Java 线程池相关问题 Java 内存模型相 ...

  7. JAVA线程池管理及分布式HADOOP调度框架搭建

    平时的开发中线程是个少不了的东西,比如tomcat里的servlet就是线程,没有线程我们如何提供多用户访问呢?不过很多刚开始接触线程的开发攻城师却在这个上面吃了不少苦头.怎么做一套简便的线程开发模式 ...

  8. Java 线程池中的线程复用是如何实现的?

    前几天,技术群里有个群友问了一个关于线程池的问题,内容如图所示: 关于线程池相关知识可以先看下这篇:为什么阿里巴巴Java开发手册中强制要求线程池不允许使用Executors创建? 那么就来和大家探讨 ...

  9. Java 线程池详解

    构造一个线程池为什么需要几个参数?如果避免线程池出现OOM?Runnable和Callable的区别是什么?本文将对这些问题一一解答,同时还将给出使用线程池的常见场景和代码片段. 基础知识 Execu ...

最新文章

  1. [转载]js节流与防抖,防止重复提交、防止频繁重复点击
  2. hdu3987 最小割边数
  3. android自定义push通知_android通过自定义toast实现悬浮通知效果的示例代码
  4. videocapture.read()解决内存泄露_只需4个步骤,分析解决在生产环境下JVM内存泄露问题...
  5. 查看Centos7 系统位数
  6. [SoapUI] 在SoapUI中通过Groovy脚本执行window命令杀掉进程
  7. Spring Boot加入websocket后,单元测试报错(javax.websocket.server.ServerContainer not available)
  8. System.Convert 的一些事
  9. 硬盘与电线挨着会高温吗_机械硬盘时代徐徐落幕?出货量再减50万件,你还会买新机械盘吗?...
  10. C++内存详解[精]
  11. getent passwd 不能访问到 ldap 的用户
  12. 面试现场简单几道java算法题,你能写出几道?
  13. 超酷,用 Python 教你绘制皮卡丘和哆啦A梦
  14. Python:实现simpson rule辛普森法则算法(附完整源码)
  15. 我国会计计算机的发展历程,会计的发展历程是什么
  16. ITILv4 MP认证以及证书展示
  17. 美国大学计算机牛校简介
  18. fcpx插件:stupid raisins info pop for mac(27个信息和描述标题栏)
  19. iOS开发实现资源包增量更新
  20. 一知半解学CubeMX——创建工程

热门文章

  1. sql判断整除_SQL操作符/运算符
  2. ad如何绘制拼版_Altium Design PCB拼板完整教程,这样讲就明白了!
  3. PGA Tour PRize Money(Uva 207)
  4. OO,OO以后,及其极限
  5. 使用 FFmpeg(bilibili视频blv合成mp4)
  6. mysql eva模型_1.初识mysql
  7. 论文笔记 | Learning Deep Features for Discriminative Localization
  8. github 首页 html,GitHub - merrier/github-html-preview: 一个可以直接预览html页面的chrome插件...
  9. mysql命令行配置
  10. 编程之美 - 中国象棋将帅问题