ThreadPool线程池

  • 1.线程池的优势
    • 1.1.引言
    • 1.2.为什么要使用线程池
  • 2.线程池的使用
    • 2.1.架构说明
    • 2.2.线程池的三大方法
      • 2.2.1.newFixedThreadPool(int)方法
      • 2.2.2.newSingleThreadExector
      • 2.2.3.newCachedThreadPool
  • 3.ThreadPoolExecutor底层原理
  • 4.线程池7大重要参数

1.线程池的优势

1.1.引言

与数据库线程池类似,如果没有数据库连接池,那么每次对数据库的连接池都要new来获取连接池。重复的连接和释放操作会消费大量的系统资源,我们可以使用数据库连接池,直接去池中取连接池。
同样,在没有线程池之前,我们也是通过new Thread.start()来获取线程,现在我们也不需要new了,这样就能实现复用,使得我们系统变得更加高效。

1.2.为什么要使用线程池

例子:

  • 10年前单核CPU电脑,假的多线程,像马戏团小丑玩多个球,CPU需要来回切换。
  • 现在是多核电脑,多个线程各自跑在独立的CPU上,不用切换效率高。

线程池的优势:

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

它的主要特点为:

  • 线程复用
  • 控制最大并发数
  • 管理线程

优点:

  • 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
  • 第二:提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
  • 第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

2.线程池的使用

2.1.架构说明

Executor 框架是什么?

Java Doc中是这么描述的

An object that executes submitted Runnable tasks. This interface provides a way of decoupling task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc. An Executor is normally used instead of explicitly creating threads.

执行提交的Runnable任务的对象。这个接口提供了一种将任务提交与如何运行每个任务的机制,包括线程的详细信息使用、调度等。通常使用Executor而不是显式地创建线程。

Java中的线程池是通过Executor框架实现的,,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类。而我们常用的接口是ExecutorService子接口Executors是线程的工具类(类似数组的工具类Arrays,集合的工具类Collections)ThreadPoolExecutor是这些类的重点我们可以通过辅助工具类Executors拿到ThreadPoolExecutor线程池

各个类更加详细的介绍如下:

Executor所有线程池的接口,只有一个方法,该接口定义执行Runnable任务的方式
ExecutorService 增加Executor的行为,是Executor实现类的最直接的接口,该接口定义提供对Executor的服务
Executors 线程池工厂类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了
ScheduledExecutorService:定时调度接口。
AbstractExecutorService 执行框架抽象类。
ThreadPoolExecutor JDK中线程池的具体实现,一般用的各种线程池都是基于这个类实现的

2.2.线程池的三大方法

2.2.1.newFixedThreadPool(int)方法

Exectors.newFixedThreadPool(int) -->执行长期任务性能好,创建一个线程池,一池有N个固定的线程,有固定线程数的线程

 public static void main(String[] args) {//一池5个受理线程,类似一个银行5个受理窗口。不管你现在多少个线程,都只有5个ExecutorService threadPool=Executors.newFixedThreadPool(5); try {//模拟有10个顾客过来银行办理业务,目前池子里面有5个工作人员提供服务。for(int i=1;i<=10;i++){//execute方法里面有一个参数,参数类型是Runnable,Runnable是函数式接口,可以用lambda表达式,并且runnable就是这10个顾客threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+"\t 办理业务");});}} catch (Exception e) {// TODO: handle exception}finally{threadPool.shutdown();} }


可以看到执行结果。池子中有5个线程,相当于5个工作人员对外提供服务,办理业务。图中1号窗口办理了两次业务,银行的受理窗口可以多次被复用。也不一定是每个人办理两次,而是谁办理的快谁就办理的多。

当我们再线程执行的过程中加400ms的延迟,可以看看效果

public static void main(String[] args) {//一池5个受理线程,类似一个银行5个受理窗口。不管你现在多少个线程,都只有5个ExecutorService threadPool=Executors.newFixedThreadPool(5); try {//模拟有10个顾客过来银行办理业务,目前池子里面有5个工作人员提供服务。for(int i=1;i<=10;i++){//execute方法里面有一个参数,参数类型是Runnable,Runnable是函数式接口,可以用lambda表达式,并且runnable就是这10个顾客threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+"\t 办理业务");});try {TimeUnit.MILLISECONDS.sleep(400);} catch (Exception e) {// TODO: handle exceptione.printStackTrace();}}} catch (Exception e) {// TODO: handle exception}finally{threadPool.shutdown();}   }


此时说明网络拥堵的情况下或者办理业务比较慢,则线程池办理业务任务分配情况比较平均。

2.2.2.newSingleThreadExector

Exectors.newSingleThreadExector()–>一个任务一个任务的执行,一池一线程

public static void main(String[] args) {//一池一个工作线程,类似一个银行有1个受理窗口ExecutorService threadPool=Executors.newSingleThreadExecutor();  try {//模拟有10个顾客过来银行办理业务for(int i=1;i<=10;i++){//execute方法里面有一个参数,参数类型是Runnable,Runnable是函数式接口,可以用lambda表达式,并且runnable就是这10个顾客threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+"\t 办理业务");});}} catch (Exception e) {// TODO: handle exception}finally{threadPool.shutdown();}
}

2.2.3.newCachedThreadPool

Exectors.newCachedThreadPool()–>执行很多短期异步任务,线程池根据需要创建新线程,但在先前构建的线程可用时将重用他们。可扩容,遇强则强。一池n线程,可扩容,可伸缩,cache缓存的意思
那么池的数量应该设置多少呢,如果银行只有一个窗口,那么当人来得太多了,就忙不过来。如果银行有很多个窗口,但是人来的少,此时又显得浪费资源。那么如何该合理安排呢?这就需要用到newCachedThreadPool()方法,可扩容,可伸缩

public static void main(String[] args) {//一池一个工作线程,类似一个银行有n个受理窗口ExecutorService threadPool=Executors.newCachedThreadPool();  try {//模拟有10个顾客过来银行办理业务for(int i=1;i<=10;i++){//execute方法里面有一个参数,参数类型是Runnable,Runnable是函数式接口,可以用lambda表达式,并且runnable就是这10个顾客threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+"\t 办理业务");});}} catch (Exception e) {// TODO: handle exception}finally{threadPool.shutdown();}  }

public static void main(String[] args) {//一池一个工作线程,类似一个银行有n个受理窗口ExecutorService threadPool=Executors.newCachedThreadPool();  try {//模拟有10个顾客过来银行办理业务for(int i=1;i<=10;i++){try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedException e){e.printStackTrace();}//execute方法里面有一个参数,参数类型是Runnable,Runnable是函数式接口,可以用lambda表达式,并且runnable就是这10个顾客threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+"\t 办理业务");});}} catch (Exception e) {// TODO: handle exception}finally{threadPool.shutdown();}    }

3.ThreadPoolExecutor底层原理

newFixedThreadPool底层源代码

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

可以看到,底层的参数包含LinkedBlockingQueue阻塞队列。

newSingleThreadExecutor底层源代码

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

newCachedThreadPool底层源代码

public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}

SynchronousQueue这个阻塞队列是单一版阻塞队列,阻塞队列的容量为1.

这3个方法其实都共同返回了一个对象,即ThreadPoolExecutor的对象

4.线程池7大重要参数

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)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.acc = System.getSecurityManager() == null ?null :AccessController.getContext();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}

上面的int corePoolSize,int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory,
RejectedExecutionHandler handler即我们的七大线程参数
上面是ThreadPoolExecutor类的构造方法,有7大参数:

1)corePoolSize:线程池中的常驻核心线程数,简称核心数。
比如说,一个线程池我们可以把它当作银行的网点,银行只要开门,就必须至少有一个人在值班,这个就叫常驻核心线程数。比如如果某个银行周一到周五五个网点全开,那么周一到周五的常驻核心线程数为5.如果今天业务没有那么频繁,窗口为1,那么今天的常驻核心线程数就是1

2)maxImumPoolSize:线程池中能够容纳同时执行的最大线程数,此值必须大于等于1

3)keepAliveTime:多余的空闲线程的存活时间,当前池中线程数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余线程会被销毁直到剩下corePoolSize为止
如果线程池中有常驻线程数,又有最大线程数,说明平时是用常驻的,工作紧张了,它会扩容到最大线程数,如果业务降下来了,我们设置了多余的空闲线程的存活时间,比如设置30s,如果30s都没有多余的请求过来,有些银行就会关闭窗口,所以它不仅会扩大还会缩小。

4)unit:keepAliveTime的单位
单位:是秒,毫秒,微秒。

5)workQueue:任务队列,被提交但尚未被执行的任务
这是一个阻塞队列,比如说银行,只有3个受理窗口,而来了4个客户。这个阻塞队列就是银行的候客区,来了客户不能让他走了。窗口数控制了线程的并发数。

6)threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认即可
线程都是统一的创建。线程池里面有已经new好的线程,这些由线程池工厂生产。

7)handler:拒绝策略,表示当前队列满了,并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝请求执行的runnable的策略

比如说今天银行客流高峰,三个窗口都满了,候客区也满了。我们没有选择继续拉人,因为不安全我们选择委婉的拒绝。

在下一节我们将介绍线程池底层工作原理

Java并发编程-ThreadPool线程池相关推荐

  1. 【Java 并发编程】线程池机制 ( ThreadPoolExecutor 线程池构造参数分析 | 核心线程数 | 最大线程数 | 非核心线程存活时间 | 任务阻塞队列 )

    文章目录 前言 一.ThreadPoolExecutor 构造参数 二.newCachedThreadPool 参数分析 三.newFixedThreadPool 参数分析 四.newSingleTh ...

  2. 【Java 并发编程】线程池机制 ( 线程池示例 | newCachedThreadPool | newFixedThreadPool | newSingleThreadExecutor )

    文章目录 前言 一.线程池示例 二.newCachedThreadPool 线程池示例 三.newFixedThreadPool 线程池示例 三.newSingleThreadExecutor 线程池 ...

  3. (转)Java并发编程:线程池的使用

    背景:线程池在面试时候经常遇到,反复出现的问题就是理解不深入,不能做到游刃有余.所以这篇博客是要深入总结线程池的使用. ThreadPoolExecutor的继承关系 线程池的原理 1.线程池状态(4 ...

  4. [转]Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  5. Java并发编程一线程池简介

    推荐:Java并发编程汇总 Java并发编程一线程池简介 为什么我们需要使用线程池? 我们知道线程是一种比较昂贵的资源,我们通过程序每创建一个线程去执行,其实操作系统都会对应地创建一个线程去执行我们的 ...

  6. Java并发编程一线程池的五种状态

    推荐:Java并发编程汇总 Java并发编程一线程池的五种状态 原文地址 Java多线程线程池(4)–线程池的五种状态 正文 线程池的5种状态:Running.ShutDown.Stop.Tidyin ...

  7. Java并发编程:线程池的使用

    在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统 ...

  8. Java并发编程之线程池及示例

    1.Executor 线程池顶级接口.定义方法,void execute(Runnable).方法是用于处理任务的一个服务方法.调用者提供Runnable 接口的实现,线程池通过线程执行这个 Runn ...

  9. 《Java 并发编程》线程池

    <Java 并发编程>专栏索引

最新文章

  1. 在纸上写好一个c语言程序后,上机运行的基本步骤为,c基本概念(选择题).docx
  2. Entity Framework Core 2.0 使用代码进行自动迁移
  3. 2020年十大币预测_2020年十大商业智能工具
  4. Diango博客--24.单元测试:测试评论应用
  5. mysql中group小计与合计_用SQL实现统计报表中的“小计”和“合计”
  6. 苦于E63没有秒表,网上找的不好用,就自己写个简单的
  7. 站群的建立,是一个长期的工作,一年时间吧
  8. 使用POI为Word文档设置阿拉伯数字形式、罗马数字形式的页脚
  9. Java根据出生年月日计算年龄
  10. 怎么开通附近小程序-微信小程序开发-视频教程20
  11. 小程序项目:基于微信小程序的答题系统——计算机毕业设计
  12. Java学习软件安装详解
  13. vue导出word纯前端实现
  14. Free Lossless Audio Codec
  15. DragonBoard 410c开发套件试用上手报告-搭建一个家庭智能管家的大脑
  16. box-shadow使用
  17. opencv(python)使用knn最近邻算法识别手写数字
  18. python职业规划书范文_大学机器人工程专业职业生涯规划范文(8篇)
  19. swift单元测试(三)XCTest之UI测试UITest
  20. 推荐5款免费的高清壁纸

热门文章

  1. 旅程落幕!网易相册将停止运营 这里有你的回忆吗?
  2. 华为下一代机皇曝光:全新麒麟985+55W超级快充
  3. 特斯拉又有新游戏可以玩了 网友:行车打游戏,亲人两行泪
  4. 剑指offer之java缓存总结,从单机缓存到分布式缓存架构
  5. vue 给checkbox 赋值_浅谈vue中关于checkbox数据绑定v-model指令的个人理解
  6. ffmpeg 编码如何做带宽控制输出
  7. Qt实践录:常见控件操作示例1
  8. 我的docker随笔17:使用docker-compose启动MySQL、Redis和Mongo
  9. cad文字提取到excel_别怕!CAD表格与EXCEL之间的转化,有它就够了
  10. centos7 安装mysql8_CentOS 下 MySQL 8.0 安装部署,超详细!