深入浅出JAVA线程池使用原理1
前言:
Java中的线程池是并发框架中运用最多的,几乎所有需要异步或并发执行任务的程序都可以使用线程池,线程池主要有三个好处:
1、降低资源消耗:可以重复使用已经创建的线程降低线程创建和销毁带来的消耗
2、提高响应速度:执行任务时,不需要等待线程的创建就可以直接执行任务
3、提高线程的可管理性:线程是稀缺资源,如果无限制地创建不仅会消耗系统资源,还会降低系统的稳定性,线程池可以对线程进行统一分配、调优和监控
一、线程池的实现原理
在了解线程池实现原理之前,先了解线程池的一些元素
1.核心线程池
线程池有一个核心线程池,核心线程池的大小在线程池创建时设定,默认是有任务提交时会创建线程来执行,也可以调用线程池的prestartAllCoreThreads来提前创建并启动所有的基本线程。
2.任务队列
当核心线程池中的线程数超过设置的的大小时,再有新的任务提交则会先将任务当道队列中,等待核心线程池中的线程执行当前的任务之后,再到队列中获取任务来执行
3.饱和策略
当线程池已经处于饱和状态,无法再分配线程给新的任务时,会采用饱和策略拒绝新的任务
1.1.线程池的工作流程
线程池的整体工作流程如下图示
当有新任务提交到线程池时,线程池的处理流程如下:
1.判断核心线程池是否已满,如果没满,则创建新线程来执行此任务(即使当前有空闲的线程也会直接创建,而不是使用空闲的线程来执行),直到核心线程池中的线程数达到了设置的大小之后就不再创建;
如果核心线程池已经满了,则进入下一阶段的判断
2.判断工作队列是否已经满了,如果工作队列没有满,则将任务暂时存放到工作队列中,等待核心线程池中的线程空闲下来再来获取任务执行(核心线程池中的线程执行完任务之后会循环从工作队列中取任务来执行);如果队列也满了,则进入下一阶段的判断
3.判断线程池的是否已满(线程池除了核心线程池,还设置了线程池的最大线程大小,即使核心线程池满了,还是可以再创建线程),如果线程池中工作的线程没有达到最大值,则创建新线程来执行任务;
如果线程池也已经满了, 则按照线程池的饱和策略来处理任务(具体怎么处理任务按具体的饱和策略来执行)
1.2、线程池的创建
创建线程池可以通过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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
这里涉及到了7个参数,含义分别如下:
corePoolSize:核心线程池大小,核心线程池的用法上面已经提到,不再赘述
maximumPoolSize:线程池的最大数量,也就是线程池允许创建的最大线程数,如果线程池的工作队列满了,则会先判断是否达到了最大线程数,若没有则还可以再创建线程来执行新任务,直到线程数达到线程池的最大数量
keepAliveTime:保持线程活动时间,当核心线程池中的线程处于空闲状态时,不会立即销毁线程而是保持一定的活动时间来等待任务,一定程度上提高了线程的复用率。
unit:线程活动时间(keepAliveTime)的单位,可以选择DAYS(天)、(HOURS)时、(MINUTES)分、(SECONDS)秒、(MILLSECONDS)毫秒、(MICROSECONDS)微妙、(NANOSECONDS)纳秒
workQueue:工作队列,核心线程池满了,新任务会存放在工作队列中等待核心线程池中的线程来获取(后面会详细描述)
threadFactory:线程工厂,线程池可以设置指定的线程工厂来创建新线程。如果不设置,默认是使用Executors.defaultThreadFactory()来创建
handler:饱和策略,当线程池已经满了,则说明线程池已经处于饱和状态无法再接受新任务了,那么就采取饱和策略来处理新任务,默认使用AbortPolicy,表示无法处理新任务而直接抛出异常(后面会详细描述)
1.3、线程池提交任务
线程池有两个方法可以用来提交任务,分别是execute()和submit()
execute()方法用于提交不需要返回值的方法,所以无法判断任务是否被线程池执行成功
submit()方法用于提交需要返回值的方法,线程池会返回一个future类型的对象,通过future对象可以判断任务是否执行成功,并且可以通过get方法来获取线程执行的返回值,get方法会阻塞当前线程直到任务结束,也可以给get方法设置指定时长,
则达到指定时长之后会立即返回,但是此时可能线程还没有执行完。
1.4、关闭线程池
线程池关闭方法有shutDown方法和shutDownNow方法,原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,如果线程无法响应中断的任务就可能永远无法终止。
shutDown方法是将线程池的状态设置为SHUTDOWN状态,然后中断所有还没有执行的任务线程
showDownNow是将线程池中的状态设置为STOP,然后尝试停止所有正在执行或空闲的线程,并返回等待执行任务的列表
如果需要保证任务执行完,则建议使用shutDown方法来执行关闭方法
1.5、线程池的监控
线程池有多个属性可和方法来监控当前线程池的状态
taskCount:线程池需要执行的任务总数量(包括等待执行和已经执行完的)
completedTaskCount:线程池已完成的任务数量
largestPoolSize:线程池运行中创建的最大线程数,可以判断线程池运行过程中是否达到了最大线程数
getPoolSize:线程池中的线程数量
getActiveCount:获取活动的线程数
二、线程池工作队列和饱和策略
2.1、工作队列 (BlockingQueue<Runnable>),由名字可以看出线程池的工作队列是一个阻塞队列,主要有以下四种类型:
ArrayBlockingQueue:基于数组结构的有界阻塞队列,采用FIFO(先进先出)原则对任务进行排序
LinkedBlockingQueue:基于链表结构的阻塞队列,采用FIFO(先进先出)原则对任务进行排序,吞吐量要高于ArrayBlockingQueue
SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入一直处于阻塞状态,吞吐量高于LinkedBlockingQueue
PriorityBlockingQueue:一个具有优先级的无限阻塞队列
2.2、饱和策略(RejectedExecutionHandler)当线程池无法处理新任务时,就将采用饱和策略来拒绝任务的执行,主要有以下四种类型:
AbortPolicy:拒绝任务,抛出异常,也是线程池默认饱和策略
CallerRunsPolicy:拒绝任务,使用调用者的当前线程来执行此任务
DiscardOldestPolicy:丢弃工作队列中的最近一个任务,并处理当前任务
discardPolicy:不做任何处理,直接丢弃任务
三、线程池实战(测试案例)
先创建一个任务类,代码如下:
public static class ThreadPoolTaskTest implements Runnable
{
private int taskIndex;//任务编号 public ThreadPoolTaskTest(int i)
{
taskIndex = i;
} @Override
public void run()
{
try
{
//线程睡眠2秒
Thread.sleep(2000);
System.out.println("线程:"+Thread.currentThread().getName()+"执行任务:"+taskIndex);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
} }
public static void main(String[] args)
{
/**
* 1.创建线程池
* 核心线程池大小:2、 最大线程池大小:4、 工作队列:有界阻塞队列,队列大小为6 线程活动保持时间:10、 活动时间单位:毫秒、 线程工厂:默认、 饱和策略:默认
*/
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(6));
threadPoolTest(executor);
} public static void threadPoolTest(ThreadPoolExecutor executor){
/**
* 前提:核心线程数为2,队列为6,而最大线程池为4
* 测试:向线程池中提交10个任务
* 猜想:核心线程池创建2个线程执行2个任务,6个任务放到队列中然后继续等待执行,2个任务被线程池创建的其他两个线程执行
* */
for (int i = 0; i < 10; i++)
{
executor.execute(new ThreadPoolTaskTest(i));
}
}
执行结果如下:
线程:pool-1-thread-3执行任务:8
线程:pool-1-thread-1执行任务:0
线程:pool-1-thread-2执行任务:1
线程:pool-1-thread-4执行任务:9
线程:pool-1-thread-1执行任务:4
线程:pool-1-thread-4执行任务:5
线程:pool-1-thread-3执行任务:2
线程:pool-1-thread-2执行任务:3
线程:pool-1-thread-1执行任务:6
线程:pool-1-thread-4执行任务:7
当将任务数量增加到12个,则核心线程池2个,工作队列6个,再创建两个线程执行2个,还有2个任务将会被饱和策略直接拒绝,结果如下
1 Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.lucky.test.jvmtest.ThreadPoolTest$ThreadPoolTaskTest@33909752 rejected from java.util.concurrent.ThreadPoolExecutor@55f96302[Running, pool size = 4, active threads = 4, queued tasks = 6, completed tasks = 0]
2 at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
3 at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
4 at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
5 at com.lucky.test.jvmtest.ThreadPoolTest.threadPoolTest1(ThreadPoolTest.java:32)
6 at com.lucky.test.jvmtest.ThreadPoolTest.main(ThreadPoolTest.java:21)
线程:pool-1-thread-4执行任务:9
线程:pool-1-thread-1执行任务:0
线程:pool-1-thread-3执行任务:8
线程:pool-1-thread-2执行任务:1
线程:pool-1-thread-2执行任务:5
线程:pool-1-thread-1执行任务:3
线程:pool-1-thread-3执行任务:4
线程:pool-1-thread-4执行任务:2
线程:pool-1-thread-1执行任务:7
线程:pool-1-thread-2执行任务:6
修改饱和策略,采用CallerRunsPolicy饱和策略,则其他十个任务正常执行,另外两个任务将会由提交任务的线程来执行任务,结果如下:
线程:main执行任务:10
线程:main执行任务:11
线程:pool-1-thread-2执行任务:1
线程:pool-1-thread-3执行任务:8
线程:pool-1-thread-4执行任务:9
线程:pool-1-thread-1执行任务:0
线程:pool-1-thread-1执行任务:3
线程:pool-1-thread-4执行任务:4
线程:pool-1-thread-3执行任务:2
线程:pool-1-thread-2执行任务:5
线程:pool-1-thread-4执行任务:7
线程:pool-1-thread-1执行任务:6
总结:本文主要整理了线程池的基本知识点及大致用法,下篇将针对Executor框架的多种线程池分别整理
深入浅出JAVA线程池使用原理1的更多相关文章
- 深入浅出JAVA线程池使用原理2
一.Executor框架介绍 Executor框架将Java多线程程序分解成若干个任务,将这些任务分配给若干个线程来处理,并得到任务的结果 1.1.Executor框架组成 任务:被执行任务需要实现的 ...
- 深入浅出Java线程池:源码篇
前言 在上一篇文章深入浅出Java线程池:理论篇中,已经介绍了什么是线程池以及基本的使用.(本来写作的思路是使用篇,但经网友建议后,感觉改为理论篇会更加合适).本文则深入线程池的源码,主要是介绍Thr ...
- Java线程池的原理及几类线程池的介绍
刚刚研究了一下线程池,如果有不足之处,请大家不吝赐教,大家共同学习.共同交流. 在什么情况下使用线程池? 单个任务处理的时间比较短 将需处理的任务的数量大 使用线程池的好处: 减少在创建和销毁线程上所 ...
- 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)
在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...
- Java 线程池的原理与实现 (转)
最近在学习线程池.内存控制等关于提高程序运行性能方面的编程技术,在网上看到有一哥们写得不错,故和大家一起分享. [分享]Java 线程池的原理与实现 这几天主要是狂看源程序,在弥补了一些以前知 ...
- Java线程池实现原理及其在美团业务中的实践
本文转载自Java线程池实现原理及其在美团业务中的实践 导语 随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供 ...
- 我眼中的java线程池实现原理
最近在看java线程池实现方面的源码,在此做个小结,因为网上关于线程池源码分析的博客挺多的,我也不打算重复造轮子啦,仅仅用纯语言描述的方式做做总结啦! 个人认为要想理解清楚java线程池实现原理,明白 ...
- Java 线程池(ThreadPoolExecutor)原理分析与使用
在我们的开发中"池"的概念并不罕见,有数据库连接池.线程池.对象池.常量池等等.下面我们主要针对线程池来一步一步揭开线程池的面纱. 使用线程池的好处 1.降低资源消耗 可以重复利用 ...
- Java 线程池(ThreadPoolExecutor)原理解析
在我们的开发中“池”的概念并不罕见,有数据库连接池.线程池.对象池.常量池等等.下面我们主要针对线程池来一步一步揭开线程池的面纱. 有关java线程技术文章还可以推荐阅读:<关于java多线程w ...
随机推荐
- C#中Timer定时器的使用示例
关于C#中timer类 在C#里关于定时器类就有3个: 1.定义在System.Windows.Forms里 2.定义在System.Threading.Timer类里 3.定义在System.Tim ...
- 源码分析八( hashmap工作原理)
首先从一条简单的语句开始,创建了一个hashmap对象: Map<String,String> hashmap = new HashMap<String,String>(); ...
- Kafka认证权限配置(动态添加用户)
之前写过一篇Kafka ACL使用实战,里面演示了如何配置SASL PLAINTEXT + ACL来为Kafka集群提供认证/权限安全保障,但有一个问题经常被问到:这种方案下是否支持动态增加/移除认证 ...
- vMware 按装 MacOs
大概思路:1.vMware11 下载以管理员运行2.服务项按名称排序把四荐停止运行3.插件unlock 以管理员运行4.载入apple Mac os x 10.11文件5.打开虚拟机 wzfou如果报 ...
- Redis密码设置与访问限制
https://www.cnblogs.com/ghjbk/p/7682041.html https://ruby-china.org/topics/28094
- php操作redis案例
<?php //实例化 $redis = new Redis(); //连接服务器 //默认端口是6379,可不写 $redis->connect( ...
- PHP(一般标签介绍,标签特性,实体名称,绝对路径与相对路径)
h1:为标题 h1~h6 标题会逐渐变小 需更换标签里面的数字 如: <h1>这是标题123</h1>---标题 <h2>这是标题123</h2>-- ...
- Python学习之旅(三十四)
Python基础知识(33):网络编程(Ⅱ) UDP编程 相对TCP,UDP则是面向无连接的协议 使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包 虽然用UDP传 ...
- Python学习之旅(二十)
Python基础知识(19):面向对象高级编程(Ⅱ) 定制类 形如“__xx__”的变量或函数在Python中是有特殊用途的 1.__str__ 让打印出来的结果更好看 __str__:面向用户:__ ...
- HTML02单词
form:表单action:行动(提交的路径)method:方法(提交的方式)input:输入type:类型text:文本(文本输入项)password:密码radio:单选按钮checkbox:复选 ...