Java 并发系列之十:java 并发框架(2个)
1. Fork/Join框架
2. Executor框架
3. ThreadPoolExecutor
4. ScheduledThreadPoolExecutor
5. FutureTask
6. txt
java并发框架
Fork/Join框架
定义
一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架
核心思想
分治
fork分解任务,join收集任务
工作窃取算法
定义
工作窃取算法work-stealing: 某个线程从其他队列里窃取任务来执行
背景
将一个不较大的任务分割为若干个互不依赖的子任务,为了减少线程之间的竞争,把这些子任务分别放到不同的队列里,并未每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。
执行快的线程帮助执行慢的线程执行任务,提升整个任务效率
为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列
窃取任务线程永远从双端队列的尾部拿任务执行
被窃取任务线程永远从双端队列的头部拿任务执行
优点
充分利用线程进行并行计算,减少了线程间的竞争
缺点
在某些情况下还是存在竞争,比如双端队列里只有一个任务时,并且该算法会消耗了更多的资源,比如创建多个线程和多个双端队列
Fork/Join框架设计
2大步骤
分割任务
fork类把大任务分割成子任务,子任务继续分割,直到子任务足够小
执行任务并合并结果
分割的子任务放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据。
核心类
ForkJoinTask
子类,用于继承
继承子类 RecursiveAction
用于没有返回结果的任务
继承子类 RecursiveTask
用于有返回结果的任务
方法
fork
分解任务
join
合并任务结果
isCompletedAbnormally()
检查任务是否已经抛出异常或已经被取消了
getException()
获取异常
ForkJoinWorkerThread
执行任务的工作线程
ForkJoinPool
执行任务ForkJoinTask的线程池,ForkJoinTask需要通过ForkJoinPool来执行
submit(task);
内部结构
ForkJoinTask数组
存放任务
ForkJoinWorkerThread数组
执行任务
Executor框架
两级调度模型
上层:Java多线程程序通常把应用分解为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程
底层: 操作系统内核OSkernel将这些线程映射到硬件处理器CPU上。
Executor框架控制上层的调度,下层的调度由操作系统内核控制,下层的调度不受应用程序的控制
Executor框架的结构
任务
包括被执行任务需要实现的接口
Runnable接口
Callable接口
任务的执行
任务执行机制的核心接口
Executor接口
继承自Executor的接口
ExecutorService接口
execute(Runnable command)
submit(Runnable task)
submit(Callable<T>task)
返回值是FutureTask对象
实现了ExecutorService接口的实现类
ThreadPoolExecutor
ScheduledThreadPoolExecutor
异步计算的结果
Future接口
get
等待任务执行完成
cancel
取消任务完成
实现了Future接口的实现类
FutureTask
Executor框架的成员
Executor是一个接口,是框架的基础,将任务的提交和任务的执行分离开来
Runnable接口和Callable接口的实现类
任务
Runnable接口
不会返回结果
Callable接口
会返回结果
工厂类Executor可以把一个Runnable包装成一个Callable
callable(Runnable task)
返回值是null
callable(Runnable task, T result)
返回值是result对象
ThreadPoolExecutor
是线程池的核心实现类,用来执行被提交的任务
ScheduledThreadPoolExecutor
可以在给定的延迟后运行命令,或者定期执行命令
Future接口和实现Future接口的FutureTask
异步计算的结果
ThreadPoolExecutor
是线程池的核心实现类,用来执行被提交的任务
线程池的创建
ThreadPoolExecutor
corePoolSize
线程池中核心线程的数量。当提交一个任务时,线程池会新建一个线程来执行任务,直到当前线程数等于corePoolSize。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
maximumPoolSize
线程池中允许的最大线程数。线程池的阻塞队列满了之后,如果还有任务提交,如果当前的线程数小于maximumPoolSize,则会新建线程来执行任务。注意,如果使用的是无界队列,该参数也就没有什么效果了。
keepAliveTime
线程空闲的时间。线程的创建和销毁是需要代价的。线程执行完任务后不会立即销毁,而是继续存活一段时间:keepAliveTime。默认情况下,该参数只有在线程数大于corePoolSize时才会生效。
unit
keepAliveTime的单位。TimeUnit
workQueue
用来保存等待执行的任务的阻塞队列,等待的任务必须实现Runnable接口。我们可以选择如下几种:
分类
ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。
LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。
SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作,反之亦然。
PriorityBlockingQueue:具有优先界别的无界阻塞队列。
threadFactory
用于设置创建线程的工厂。可以通过线程工厂给每个创建出来的线程设置更有意义的名字,该对象可以通过Executors.defaultThreadFactory()
handler
RejectedExecutionHandler,线程池的拒绝策略。所谓拒绝策略,是指将任务添加到线程池中时,线程池拒绝该任务所采取的相应策略。当向线程池中提交任务时,如果此时线程池中的线程已经饱和了,而且阻塞队列也已经满了,则线程池会选择一种拒绝策略来处理该任务。
四种拒绝策略
AbortPolicy:直接抛出异常,默认策略;
CallerRunsPolicy:用调用者所在的线程来执行任务;
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
DiscardPolicy:直接丢弃任务;
当然我们也可以实现自己的拒绝策略,例如记录日志、持久化存储不能处理的任务等等,实现RejectedExecutionHandler接口自定义即可。
工厂类Executors创建3种类型的ThreadPoolExecutor
SingleThreadExecutor
使用单个worker线程的Executor
应用场景
适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景
特点
corePool和maximumPoolSize均被设置为1
使用的是相当于无界的有界阻塞队列LinkedBlockingQueue,所以带来的影响和FixedThreadPool一样。
FixedThreadPool
固定线程数的线程池
应用场景
为了满足资源管理的需求,需要限制当前线程数量的应用场景
适用于负载比较重的服务器
特点
corePoolSize 和 maximumPoolSize都设置为创建FixedThreadPool时指定的参数nThreads,意味着当线程池满时且阻塞队列也已经满时,如果继续提交任务,则会直接走拒绝策略
默认的拒绝策略,即AbortPolicy,则直接抛出异常。
keepAliveTime设置为0L,表示空闲的线程会立刻终止。
workQueue则是使用LinkedBlockingQueue,但是没有设置范围,那么则是最大值(Integer.MAX_VALUE),这基本就相当于一个无界队列了。
无界队列对线程池的影响
1. 当线程池中的线程数量等于corePoolSize 时,如果继续提交任务,该任务会被添加到无界阻塞队列workQueue中,因此线程中的线程数不会超过corePoolSize
2. 由于1,使用无界队列时的 maximumPoolSize是一个无效参数
3. 由于1和2,使用无界队列时的 keepAliveTime 是一个无效参数
4. 不会拒绝任务
CachedThreadPool
根据需要创建新线程,是大小无界的线程池
应用场景
适用于执行很多的短期异步任务的小程序
适用于负载较轻的服务器
特点
corePool为0,maximumPoolSize为Integer.MAX_VALUE,这就意味着所有的任务一提交就会加入到阻塞队列中。
keepAliveTime这是为60L,unit设置为TimeUnit.SECONDS,意味着空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。
阻塞队列采用的SynchronousQueue,每个插入操作都必须等待另一个线程对应的移除操作,此处把主线程提交的任务传递给空闲线程去执行。
SynchronousQueue是一个没有元素的阻塞队列,加上corePool = 0 ,maximumPoolSize = Integer.MAX_VALUE,这样就会存在一个问题,如果主线程提交任务的速度远远大于CachedThreadPool的处理速度,则CachedThreadPool会不断地创建新线程来执行任务,这样有可能会导致系统耗尽CPU和内存资源,所以在使用该线程池是,一定要注意控制并发的任务数,否则创建大量的线程可能导致严重的性能问题。
重要操作
offer
主线程执行offer操作与空闲线程执行的poll操作配对成功后,主线程把任务交给空闲线程执行
execute
执行任务
poll
让空闲线程在SynchronousQueue中等待60s,如果等待到新任务则执行,否则,空闲线程将终止
向线程池提交任务
execute()
用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功
工作原理
1. 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(执行这一步骤需要获取全局锁)
2. 如果运行的线程等于或多于corePoolSize(完成预热之后),则将任务加入BlockingQueue,这一步不需要全局锁
3. 如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务 (执行这一步骤需要获取全局锁)
4. 如果创建的线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExcution()方法
submit()
用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过它可以判定任务是否执行成功。
future.get()方法用来获取返回值,它会阻塞当前线程直到任务完成
future.get(long timeout, TimeUnit unit) 方法会阻塞当前线程一段时间后立即返回,这时候可能任务没有执行完
关闭线程池
原理
遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断地的任务可能永远无法终止
方法
shutdown
只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程
shutdownNow
首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,任务不一定要执行完
合理配置线程池
任务的性质
CPU密集型任务
N(cpu)+1 个线程的线程池
IO密集型任务
2*N(cpu) 个线程的线程池
混合型任务
拆分成一个CPU密集型任务和一个IO密集型任务
任务的优先级
高
中
低
用PriorityBlockingQueue
任务的执行时间
长
中
短
任务的依赖性
是否依赖其他系统资源,比如数据库连接
等待返回结果的时间越长,CPU空闲时间越长,那么线程数应该设置的越大,以便更好利用线程池
建议使用有界队列。有界队列能够提高系统的稳定性和预警能力 ,无界队列会直接撑爆内存,导致系统不可用
用PriorityBlockingQueue ,让执行时间短的任务先执行
线程池的监控
方便出问题时,可以根据线程池的使用情况,快速定位问题
参数
taskCount
线程池需要执行的任务数量
completedTaskCount
线程池在运行过程中已经完成的任务数量
largestPoolSize
线程池曾将创建过的最大线程数量。如果该数字等于线程池的最大大小,表示线程池曾经满过
getPoolSize
线程池的线程数量
getActiveCount
获取活动的线程数
重写方法
beforeExecute()
任务执行前
afterExecute()
任务执行后
terminated()
线程池关闭前
ScheduledThreadPoolExecutor
可以在给定的延迟后运行命令,或者定期执行命令,与Timer类似,但比其功能更强大,更灵活
ScheduledThreadPoolExecutor V.S. Timer
ScheduledThreadPoolExecutor可以再构造函数中指定多个对应的后台进程数
Timer对应的是单个后台进程
内部类
DelayedWorkQueue
所使用的阻塞队列变成了DelayedWorkQueue
DelayedWorkQueue为ScheduledThreadPoolExecutor中的内部类,它其实和阻塞队列DelayQueue有点儿类似
DelayedWorkQueue中的任务必然是按照延迟时间从短到长来进行排序的
Reentrant+Condition
ScheduledFutureTask
待调度的任务
ScheduledFutureTask内部继承FutureTask,实现RunnableScheduledFuture接口
三个比较重要的变量
private final long sequenceNumber;
/** 任务被添加到ScheduledThreadPoolExecutor中的序号 */
private long time;
/** 任务要执行的具体时间 */
private final long period;
/** 任务的间隔周期 */
compareTo方法,提供一个排序算法,该算法规则是:首先按照time排序,time小的排在前面,大的排在后面,如果time相同,则使用sequenceNumber排序,小的排在前面,大的排在后面。
compareTo()方法使用于DelayedWorkQueue队列对其元素ScheduledThreadPoolExecutor task进行排序的算法
创建
通常使用工厂类Executors来创建
2种类型
ScheduledThreadPoolExecutor
包含固定个数个线程
适用于资源管理而需要限制线程数的场景
SingleThreadScheduledExecutor
包含一个线程
适用于单个线程,需要保证顺序执行各个任务的场景
4个调度器
schedule(Callable callable, long delay, TimeUnit unit) :创建并执行在给定延迟后启用的 ScheduledFuture。
schedule(Runnable command, long delay, TimeUnit unit) :创建并执行在给定延迟后启用的一次性操作。
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) :创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。
scheduleAtFixedRate是周期固定,也就说它是不会受到这个延迟的影响的,每个线程的调度周期在初始化时就已经绝对了,是什么时候调度就是什么时候调度,它不会因为上一个线程的调度失效延迟而受到影响。
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) :创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。
scheduleWithFixedDelay是每个线程的调度间隔固定,也就是说第一个线程与第二线程之间间隔delay,第二个与第三个间隔delay,以此类推。
调度和执行
run()
1. 调用isPeriodic()获取该线程是否为周期性任务标志,然后调用canRunInCurrentRunState()方法判断该线程是否可以执行,如果不可以执行则调用cancel()取消任务。
2. 如果当线程已经到达了执行点,则调用run()方法执行task,该run()方法是在FutureTask中定义的。
3. 否则调用runAndReset()方法运行并重置状态,调用setNextRunTime()方法重新计算任务的下次执行时间,重新把任务添加到队列中,让该任务可以重复执行。
reExecutePeriodic重要的是调用super.getQueue().add(task);将任务task加入的队列DelayedWorkQueue中
FutureTask
实现了Future接口和Runnable接口,代表异步计算的结果,
因为实现了Runnable接口,可以交给Executor执行,也可以由调用线程直接执行
重要操作
run() 3种状态
未启动
已启动
已完成
正常结束
取消而结束
异常而结束
get()
3种状态
未启动
阻塞
底层是 LockSupport.park();
已启动
阻塞
底层是 LockSupport.park();
已完成
立即返回结果或者抛出异常
cannel()
3种状态
未启动
任务不会被执行
已启动
cannel(true):中断执行任务的线程
cannel(false):不中断执行任务的线程
已完成
返回false
应用场景
当一个线程需要等待另一个线程把某个任务执行完后它才能继续执行
7. 参考网址
- 参考来源:http://cmsblogs.com/wp-content/resources/img/sike-juc.png
- 《Java并发编程的艺术》_方腾飞PDF 提取码:o9vr
- http://ifeve.com/the-art-of-java-concurrency-program-1/
- Java并发学习系列-绪论
- Java并发编程实战
- 死磕 Java 并发精品合集
Java 并发系列之十:java 并发框架(2个)的更多相关文章
- java高并发系列 - 第2天:并发级别
由于临界区的存在,多线程之间的并发必须受到控制.根据控制并发的策略,我们可以把并发的级别分为阻塞.无饥饿.无障碍.无锁.无等待几种. 阻塞 一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继 ...
- Java Thread系列(十)Future 模式
Java Thread系列(十)Future 模式 Future 模式适合在处理很耗时的业务逻辑时进行使用,可以有效的减少系统的响应时间,提高系统的吞吐量. 一.Future 模式核心思想 如下的请求 ...
- Java多线程系列--“JUC锁”01之 框架
本章,我们介绍锁的架构:后面的章节将会对它们逐个进行分析介绍.目录如下:01. Java多线程系列--“JUC锁”01之 框架02. Java多线程系列--“JUC锁”02之 互斥锁Reentrant ...
- Java 设计模式系列(十五)观察者模式(Observer)
Java 设计模式系列(十五)观察者模式(Observer) Java 设计模式系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Java ...
- Java 设计模式系列(十八)备忘录模式(Memento)
Java 设计模式系列(十八)备忘录模式(Memento) 备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式.备忘录对象是一个用来存储另外一个对象内部状态 ...
- Java 设计模式系列(十五)迭代器模式(Iterator)
Java 设计模式系列(十五)迭代器模式(Iterator) 迭代器模式又叫游标(Cursor)模式,是对象的行为模式.迭代子模式可以顺序地访问一个聚集中的元素而不必暴露聚集的内部表象(interna ...
- Java 设计模式系列(十二)策略模式(Strategy)
Java 设计模式系列(十二)策略模式(Strategy) 策略模式属于对象的行为模式.其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换.策略模式使得算法可以 ...
- Java 设计模式系列(十四)命令模式(Command)
Java 设计模式系列(十四)命令模式(Command) 命令模式把一个请求或者操作封装到一个对象中.命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复 ...
- Java 设计模式系列(十)外观模式
Java 设计模式系列(十)外观模式 门面模式(Facade):外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这 ...
- Java Thread系列(十)生产者消费者模式
Java Thread系列(十)生产者消费者模式 生产者消费者问题(producer-consumer problem),是一个多线程同步问题的经典案例.该问题描述了两个共亨固定大小缓冲区的线程-即所 ...
随机推荐
- vue中操作localstorage
首先在子组件将localstorage方法进行封装 在父组件中对其进行引用 将输入的值存入到定义的searchHistory数组中,存储localstorage需要传两个参数,变量名为searchHi ...
- ElementUI如何展开指定Tree树节点
原文:https://blog.csdn.net/gaojie_csdn/article/details/80738488 [问题] 在页面使用ElementUI的时候,想做出一个主动展开树节点的效果 ...
- Struts2处理(jQuery)Ajax请求
1. Ajax Ajax(Asynchronous JavaScript and XML,异步JavaScript和XML)时一种创建交互式网页应用的网页开发技术,它并不是一项新的技术,其产生 ...
- redis的两种持久化方案
前言 人生在于折腾系列,网络,多线程等系列博客楼主还在继续折腾也不会放弃.缓存的知识其实并不仅仅在于简单的增删改查,我觉得有必要全面深入的学习一波.记录学习的过程与体悟. RDB 什么是RDB 对re ...
- 0 != null 为什么报指针?
大家好,这是我第一次写博客,来分享我平时工作中遇到的问题及平时学习的技术,如果有写的不好或者不对的地方还望大家能够指出和包涵. 那么接下来就开始说下我工作中遇到的这个问题,我写了一个test,如下: ...
- css实现图片信息展示
<style> .layui-fluid{padding: 15px;} .img-responsive{display: block;width: 100%;max-width: 100 ...
- i春秋——“百度杯”CTF比赛 十月场——GetFlag(md5碰撞、文件包含、网站绝对路径)
需要提交的captcha满足等式,肯定就是MD5碰撞了 附上脚本 import hashlib def func(md5_val): for x in range(1,100000000): md5_ ...
- Git恢复删除的分支
1.使用 git reflog 命令查看显示整个本地仓储的commit,包括所有branch的commit,甚至包括已经撤销的commit. 2.找到我们想要恢复的分支 ,可以看到我们当时commit ...
- Qt中QWidget、QDialog和QMainWindow
QWidget 类是所有用户界面对象的基类.只有一个"页面" QMainWindow 是一个"窗口".含有菜单栏.状态栏.工具栏.停靠窗口.中心窗口 QDial ...
- JVM性能优化简介
01. JVM是什么 概述: 大白话: 全称Java Virtual Machine(Java虚拟机), 它是一个虚构出来的计算机, 通过实际的计算机来模拟 ...