推荐阅读:Java线程池实现原理及其在美团业务中的实践

文章目录

    • 什么是线程池
    • 使用线程池的好处
    • 线程池的实现原理
      • 流程图分析
      • 源码分析
    • 线程池的使用
      • 向线程池中提交任务
      • newCachedThreadPool
      • newFixedThreadPool
      • newScheduledThreadPool
      • newSingleThreadExecutor
      • 自定义线程池
  • Springboot 线程池的封装使用

什么是线程池

  • 线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是继续放在线程池中,等待下一个任务。和连接池有点类似

使用线程池的好处

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

线程池的实现原理

当向线程池提交了一个任务后,线程池是如何处理这个任务的呢?
我们来看看这个流程图

从流程图中我们可以很清楚的看到

  1. 首先有任务来会判断核心线程池里的线程是否已满(大于或等于corePoolSize),如果不是,则创建一个新的线程来执行任务
  2. 是,进入下一个流程判断判断工作队列是否已经满了。如果工作队列没满,则将任务放到工作队列中
  3. 工作队列满了,判断线程池的线程是否都处理工作状态,是,则创建新的线程继续。如果创建的线程超过maximumPoolSize(线程池已满)。则交给饱和策略来处理这个任务

个人解释说明:就是首先有任务来就会在核心线程池创建线程,再来任务不管核心线程池中有没有线程空闲,都会继续创建线程,直到核心线程池满了,就会放到任务队列中,如果任务队列也满了,就会创建非核心线程,注意核心线程池的线程是不会死亡的,非核心到过期时间会被销毁

流程图分析

我们来看看 ThreadPoolExecutor执行execute

  1. 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤
    需要获取全局锁)
  2. 如果运行的线程等于或大于corePoolSize,则将任务加入到BlockingQueue
  3. 如果BlockingQueue 队列已满,则创建新的线程来处理任务(执行这一步需要获取全局锁)
  4. 如果创建新线程将使当前运行的线程超出 maximumPoolSize ,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法

ThreadPoolExecutor采取上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能
地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后
(当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而
步骤2不需要获取全局锁

源码分析

我们来看看 ThreadPoolExecutor 中 execute 方法的实现

public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();//1.当前池中线程比核心数少,新建一个线程执行任务if (workerCountOf(c) < corePoolSize) {   if (addWorker(command, true))return;c = ctl.get();}//2.核心池已满,但任务队列未满,添加到队列中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);}//3.核心池已满,队列已满,试着创建一个新线程else if (!addWorker(command, false))reject(command);    //如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
}


线程池中的线程执行任务分两种情况,如下

  1. 在execute()方法中创建一个线程时,会让这个线程执行当前任务。
  2. 这个线程执行完上图中1的任务后,会反复从BlockingQueue获取任务来执行

线程池的使用

现在常用的线程池分别是这四种:

  1. newCachedThreadPool:可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中。线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程
  2. newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
  3. newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行
  4. newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

这四种线程池都是由Executor接口创建的,而Executor接口的最顶层实现都是ThreadPoolExecutor类,创建这些不同的线程池也就是调用ThreadPoolExecutor类的构造方法传入的不同而已,所以我们先来深入了解一下ThreadPoolExecutor的构造方法

 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);}
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), handler);}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.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}
  • corePoolSize:核心线程池的大小。
  • maximunPoolSize:线程池最大数量。线程池允许创建的最大线程数。如果队列满了,并
    且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如
    果使用了无界的任务队列这个参数就没什么效果
  • keepAliveTime:非核心线程能够空闲的最长时间,超过时间,线程终止。果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率
  • unit:时间单位和keepAliveTime一起使用,可选的单位有天(DAYS)、小时(HOURS)、分钟
    (MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)
  • workQueue:缓存队列,用来存放等待被执行的任务
  • threadFactory:用于设置创建线程的工厂
  • handler:饱和策略,线程数量大于最大线程数就会采用饱和策略,五种策略为:
  1. AbortPolicy:直接抛出异常
  2. CallerRunsPolicy:只用调用者所在线程来运行任务
  3. DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
  4. DiscardPolicy:不处理,丢弃掉
  5. 根据应用场景需要来实现RejectedExecutionHandler接口自定义策略

向线程池中提交任务

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

newCachedThreadPool

通过构造方法我们可以看到newCachedThreadPool线程池是无界的,60秒回收

public class Test {public static void main(String[] args) {ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {try {Thread.sleep(100);} catch (Exception e) {// TODO: handle exception}newCachedThreadPool.execute(()->{System.out.println(Thread.currentThread().getName() + "正在执行");});}//不熟悉lambda使用这种方式/* for (int i = 0; i < 10; i++) {newCachedThreadPool.execute(new Runnable() {@Overridepublic void run() {try {Thread.sleep(100);} catch (Exception e) {// TODO: handle exception}System.out.println(Thread.currentThread().getName() + "正在执行");}});}*/}
}

可以看到始终是线程1在被复用

newFixedThreadPool

构造函数

可以看到最大线程数与核心线程数保持一致这样就保证了传入的线程数一直被复用

public class Test {public static void main(String[] args) {ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);}
}


可以看到始终只有两个线程池在执行

newScheduledThreadPool

构造方法

默认也是无界的

public class Test {public static void main(String[] args) {ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);for (int i = 0; i < 10; i++) {newScheduledThreadPool.schedule(()-> System.out.println(Thread.currentThread().getName() + "延迟三秒执行"),3,TimeUnit.SECONDS);}//不熟悉lambda使用这种方式/* for (int i = 0; i < 10; i++) {newScheduledThreadPool.schedule(new Runnable() {public void run() {System.out.println(Thread.currentThread().getName() + "延迟三秒执行");}}, 3, TimeUnit.SECONDS);}*/}
}

这段任务在三秒后才会开始执行

如果要循环执行,只需要把这样既可以

newScheduledThreadPool .scheduleAtFixedRate(new Runnable() {13             public void run() {System.out.println(Thread.currentThread().getName() + "延迟三秒执行后循环执行");
15             }
16         }, 3, 3, TimeUnit.SECONDS);

newSingleThreadExecutor

构造方法

可以看到核心线程数和最大线程数都为1,始终只有一个线程数

public class Test {public static void main(String[] args) {ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();for (int i = 0; i < 10; i++) {final int index = i;newSingleThreadExecutor.execute(()->{System.out.println(Thread.currentThread().getName() +"打印的值为" + index);try {Thread.sleep(200);} catch (Exception e) {// TODO: handle exception}});//不熟悉lambda使用这种方式/*newSingleThreadExecutor.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() +"打印的值为" + index);try {Thread.sleep(200);} catch (Exception e) {// TODO: handle exception}}});*/}}
}


可以看到始终只有一个线程,按顺序打印结果

自定义线程池

public class CustomThreadPool {public static void main(String[] args) {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));try {for (int i = 0; i < 6; i++) {TaskThread taskThread = new TaskThread("任务" + i);threadPoolExecutor.execute(taskThread);}} catch (Exception e) {threadPoolExecutor.shutdown();e.printStackTrace();}}}class TaskThread implements Runnable {private String taskName;public TaskThread(String taskName) {this.taskName = taskName;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+taskName);}
}


这里可以看到队列满了的情况下并且超出最大线程数,队列就执行拒绝策略

Springboot 线程池的封装使用

@Component(ThreadPoolManager.PACKAGE_BEAN_NAME)
public class ThreadPoolManager implements DisposableBean {private static final Logger logger = LoggerFactory.getLogger(ThreadPoolManager.class);public static final String PACKAGE_BEAN_NAME = "ThreadPoolManager";/*** 定义线程池*/private ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 500, 300, TimeUnit.SECONDS,new ArrayBlockingQueue<>(25),new ThreadFactoryBuilder().setDaemon(false).build(),new ThreadPoolExecutor.CallerRunsPolicy());/*** @param name* @param runnable*/public void execute(final String name, final Runnable runnable) {execute(name, runnable, null);}/*** @param name* @param runnable* @param exceptionHandler 异常处理*/public void execute(final String name, final Runnable runnable, final Thread.UncaughtExceptionHandler exceptionHandler) {threadPool.execute(() -> {final Thread currentThread = Thread.currentThread();setName(currentThread, name);if (Objects.isNull(exceptionHandler)) {//默认只打印异常日志currentThread.setUncaughtExceptionHandler((thread, exception) -> {logger.error("线程执行异常-{}", currentThread.getName(), exception);});} else {currentThread.setUncaughtExceptionHandler(exceptionHandler);}runnable.run();});}/*** 销毁线程池* @throws Exception*/@Overridepublic void destroy() throws Exception {MoreExecutors.shutdownAndAwaitTermination(threadPool, 20, TimeUnit.SECONDS);}/*** @param callable* @return submit方式提交任务,则异常不能被异常处理器捕获,如果一个由submit提交的任务由于抛出了异常而结束,那么这个异常将被Future.get封装在ExecutionException中重新抛出*/public <T> Future<T> submit(String name, final Callable<T> callable) {return threadPool.submit(() -> {setName(Thread.currentThread(), name);return callable.call();});}private void setName(Thread thread, String name) {thread.setName(name);}/*** 获取线程池配置* @return*/public JSONObject getConfig() {JSONObject data = new JSONObject();data.put("activeCount", threadPool.getActiveCount());data.put("completedTaskCount", threadPool.getCompletedTaskCount());data.put("queue.size()", threadPool.getQueue().size());data.put("taskCount", threadPool.getTaskCount());data.put("corePoolSize", threadPool.getCorePoolSize());data.put("largestPoolSize", threadPool.getLargestPoolSize());data.put("maximumPoolSize", threadPool.getMaximumPoolSize());return data;}
}

Java 线程池原理及四种常用的线程池使用相关推荐

  1. spring线程池 java_Java 中几种常用的线程池

    概述: 在java内置API中操作线程所用到的类为Thread.创建线程一般有两种方式, 继承Thread方式 实现Runnable方式,并以runnable作为target创建Thread 在And ...

  2. 内存池——第一章 几种常用的内存池技术

    几乎所有应用程序中都会有内存的分配和释放,而频繁的分配和释放内存无疑会产生内存碎片,降低系统性能,尤其对性能要求较高的程序比较明显.下面介绍几种常见的内存池技术.     一  环形缓存     环形 ...

  3. java regex match 替换_java正则表达式四种常用的处理方式(匹配、分割、替代、获取)...

    java 正则表达式高级篇,介绍四种常用的处理方式:匹配.分割.替代.获取,具体内容如下 package test; import java.util.regex.Matcher; import ja ...

  4. [转载] java实现四种常用排序算法

    参考链接: 用Java排序 四种常用排序算法 ##注:从小到大排 ##冒泡排序## 特点:效率低,实现简单 思想:每一趟将待排序序列中最大元素移到最后,剩下的为新的待排序序列,重复上述步骤直到排完所有 ...

  5. android java 多线程,Android多线程的四种方式

    当我们启动一个App的时候,Android系统会启动一个Linux Process,该Process包含一个Thread,称为UI Thread或Main Thread.通常一个应用的所有组件都运行在 ...

  6. Java SE基础(十四)常用API

    Java SE基础(十四)常用API 概述 Object类 构造方法 成员方法 toString()方法 equals(Object obj)方法 Arrays排序 Arrays类概述 Arrays成 ...

  7. Java反射之创建对象的四种方式

    Java反射之创建对象的四种方式 1.使用new关键字 2.使用Java反射机制,反射构造器 3.使用克隆方式创建对象Cloneable 4.使用序列化Serializable 1.使用Java反射机 ...

  8. HashMap遍历的四种常用方式

    古人云:温故而知新. 最近闲来无事就去翻阅了一下之前的一些基础java知识点.本想着随便看看,然而就发现有了意外收获.比如本文所讲HashMap遍历的四种常用方式. 大伙们一起学习一起进步,记得点赞关 ...

  9. 五分钟让你搞懂Nginx负载均衡原理及四种负载均衡算法

    前言 今天这篇文章介绍了负载均衡的原理以及对应的四种负载均衡算法,当然还有对应的指令及实战,欢迎品尝.有不同意见的朋友可以评论区留言! 负载均衡 所谓负载均衡,就是 Nginx 把请求均匀的分摊给上游 ...

最新文章

  1. (0081)iOS开发之无限后台定位并上传数据到服务器
  2. vue企业项目demo_基于SpringBoot和Vue的企业级中后台开源项目
  3. python注册用户名和密码登录_python实现自动登录需要用户名和密码的网站
  4. 构建之法:1、2、3章阅读后感
  5. 河南大学计算机组成原理,河南大学计算机组成原理考点.pdf
  6. 【未解决】【Linux环境】IDEA下搜狗输入法无法光标跟随
  7. [1034]安装Xposed框架+JustTrustMe
  8. protocol buffer 使用
  9. Word错别字校对-JCJC
  10. 2018.9.10 工作日志 猎宝行动
  11. java小游戏超级玛丽:07.第三关的设计
  12. 2019年上半年软件设计师下午试题
  13. php empty 和空字符串区别
  14. html5定义一个变量,JavaScript 变量
  15. 5.6.3 列表到字典的函数,针对好玩游戏的物品清单
  16. WPF 设置TextBlock 自动换行
  17. dhu复试基础——71 单词统计
  18. 小米网关与服务器的交互协议,绿米网关局域网通讯协议V108.doc
  19. GPIB简介及其地址设置
  20. uniapp 阻止事件冒泡

热门文章

  1. linux下tomcat不能启动,linux系统中的tomcat无法启动怎么解决
  2. 责任大,权力小,为什么还要当项目经理
  3. 使用pd.io.sql.to_sql 将数据导入到mysql数据库
  4. Flask 学习-47.Flask-RESTX 自定义响应内容marshal_with
  5. python根据品种散点图鸢尾花_python机器学习入门到精通--实战分析(三)
  6. python匹配数字开头的内容_python使用正则表达式匹配字符串开头并打印示例
  7. Houdini-学习之路(四)
  8. 关于留下性价比高,实用性强的产品新的思路
  9. #创新应用#傲游浏览器:无限阅读空间
  10. c primer plus(第六版) 第二章答案(vscode编译运行通过)