打赏

Java 并发系列之十:java 并发框架(2个)

1. Fork/Join框架

2. Executor框架

3. ThreadPoolExecutor

4. ScheduledThreadPoolExecutor

5. FutureTask

6. txt

  1 java并发框架
  2     Fork/Join框架
  3         定义
  4             一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架
  5          核心思想
  6              分治
  7              fork分解任务,join收集任务
  8          工作窃取算法
  9             定义
 10                  工作窃取算法work-stealing: 某个线程从其他队列里窃取任务来执行
 11             背景
 12                 将一个不较大的任务分割为若干个互不依赖的子任务,为了减少线程之间的竞争,把这些子任务分别放到不同的队列里,并未每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。
 13                  执行快的线程帮助执行慢的线程执行任务,提升整个任务效率
 14                 为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列
 15 窃取任务线程永远从双端队列的尾部拿任务执行
 16 被窃取任务线程永远从双端队列的头部拿任务执行
 17             优点
 18                 充分利用线程进行并行计算,减少了线程间的竞争
 19             缺点
 20                 在某些情况下还是存在竞争,比如双端队列里只有一个任务时,并且该算法会消耗了更多的资源,比如创建多个线程和多个双端队列
 21         Fork/Join框架设计
 22             2大步骤
 23                 分割任务
 24                     fork类把大任务分割成子任务,子任务继续分割,直到子任务足够小
 25                 执行任务并合并结果
 26                     分割的子任务放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据。
 27         核心类
 28             ForkJoinTask
 29                 子类,用于继承
 30                     继承子类 RecursiveAction
 31                         用于没有返回结果的任务
 32                     继承子类 RecursiveTask
 33                         用于有返回结果的任务
 34                 方法
 35                     fork
 36                         分解任务
 37                     join
 38                         合并任务结果
 39                     isCompletedAbnormally()
 40                         检查任务是否已经抛出异常或已经被取消了
 41                     getException()
 42                         获取异常
 43              ForkJoinWorkerThread
 44                  执行任务的工作线程
 45             ForkJoinPool
 46                  执行任务ForkJoinTask的线程池,ForkJoinTask需要通过ForkJoinPool来执行
 47                     submit(task);
 48                 内部结构
 49                     ForkJoinTask数组
 50                         存放任务
 51                     ForkJoinWorkerThread数组
 52                         执行任务
 53     Executor框架
 54         两级调度模型
 55             上层:Java多线程程序通常把应用分解为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程
 56             底层: 操作系统内核OSkernel将这些线程映射到硬件处理器CPU上。
 57             Executor框架控制上层的调度,下层的调度由操作系统内核控制,下层的调度不受应用程序的控制
 58         Executor框架的结构
 59             任务
 60                 包括被执行任务需要实现的接口
 61                     Runnable接口
 62                     Callable接口
 63             任务的执行
 64                 任务执行机制的核心接口
 65                     Executor接口
 66                 继承自Executor的接口
 67                     ExecutorService接口
 68                         execute(Runnable command)
 69                         submit(Runnable task)
 70                         submit(Callable<T>task)
 71                         返回值是FutureTask对象
 72                 实现了ExecutorService接口的实现类
 73                     ThreadPoolExecutor
 74                     ScheduledThreadPoolExecutor
 75             异步计算的结果
 76                 Future接口
 77                     get
 78                         等待任务执行完成
 79                     cancel
 80                         取消任务完成
 81                 实现了Future接口的实现类
 82                     FutureTask
 83         Executor框架的成员
 84             Executor是一个接口,是框架的基础,将任务的提交和任务的执行分离开来
 85             Runnable接口和Callable接口的实现类
 86                 任务
 87                     Runnable接口
 88                         不会返回结果
 89                     Callable接口
 90                         会返回结果
 91                         工厂类Executor可以把一个Runnable包装成一个Callable
 92                         callable(Runnable task)
 93                             返回值是null
 94                         callable(Runnable task, T result)
 95                             返回值是result对象
 96             ThreadPoolExecutor
 97                 是线程池的核心实现类,用来执行被提交的任务
 98             ScheduledThreadPoolExecutor
 99                 可以在给定的延迟后运行命令,或者定期执行命令
100             Future接口和实现Future接口的FutureTask
101                 异步计算的结果
102     ThreadPoolExecutor
103         是线程池的核心实现类,用来执行被提交的任务
104         线程池的创建
105             ThreadPoolExecutor
106                 corePoolSize
107                     线程池中核心线程的数量。当提交一个任务时,线程池会新建一个线程来执行任务,直到当前线程数等于corePoolSize。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
108                 maximumPoolSize
109                     线程池中允许的最大线程数。线程池的阻塞队列满了之后,如果还有任务提交,如果当前的线程数小于maximumPoolSize,则会新建线程来执行任务。注意,如果使用的是无界队列,该参数也就没有什么效果了。
110                 keepAliveTime
111                     线程空闲的时间。线程的创建和销毁是需要代价的。线程执行完任务后不会立即销毁,而是继续存活一段时间:keepAliveTime。默认情况下,该参数只有在线程数大于corePoolSize时才会生效。
112                 unit
113                     keepAliveTime的单位。TimeUnit
114                 workQueue
115                     用来保存等待执行的任务的阻塞队列,等待的任务必须实现Runnable接口。我们可以选择如下几种:
116                     分类
117                         ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。
118                         LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。
119                         SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作,反之亦然。
120                         PriorityBlockingQueue:具有优先界别的无界阻塞队列。
121                 threadFactory
122                     用于设置创建线程的工厂。可以通过线程工厂给每个创建出来的线程设置更有意义的名字,该对象可以通过Executors.defaultThreadFactory()
123                 handler
124                     RejectedExecutionHandler,线程池的拒绝策略。所谓拒绝策略,是指将任务添加到线程池中时,线程池拒绝该任务所采取的相应策略。当向线程池中提交任务时,如果此时线程池中的线程已经饱和了,而且阻塞队列也已经满了,则线程池会选择一种拒绝策略来处理该任务。
125                     四种拒绝策略
126                         AbortPolicy:直接抛出异常,默认策略;
127                         CallerRunsPolicy:用调用者所在的线程来执行任务;
128                         DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
129                         DiscardPolicy:直接丢弃任务;
130                     当然我们也可以实现自己的拒绝策略,例如记录日志、持久化存储不能处理的任务等等,实现RejectedExecutionHandler接口自定义即可。
131             工厂类Executors创建3种类型的ThreadPoolExecutor
132                 SingleThreadExecutor
133                     使用单个worker线程的Executor
134                     应用场景
135                         适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景
136                     特点
137                         corePool和maximumPoolSize均被设置为1
138                         使用的是相当于无界的有界阻塞队列LinkedBlockingQueue,所以带来的影响和FixedThreadPool一样。
139                 FixedThreadPool
140                     固定线程数的线程池
141                     应用场景
142                         为了满足资源管理的需求,需要限制当前线程数量的应用场景
143                         适用于负载比较重的服务器
144                     特点
145                         corePoolSize 和 maximumPoolSize都设置为创建FixedThreadPool时指定的参数nThreads,意味着当线程池满时且阻塞队列也已经满时,如果继续提交任务,则会直接走拒绝策略
146                         默认的拒绝策略,即AbortPolicy,则直接抛出异常。
147                         keepAliveTime设置为0L,表示空闲的线程会立刻终止。
148                         workQueue则是使用LinkedBlockingQueue,但是没有设置范围,那么则是最大值(Integer.MAX_VALUE),这基本就相当于一个无界队列了。
149                         无界队列对线程池的影响
150                             1. 当线程池中的线程数量等于corePoolSize 时,如果继续提交任务,该任务会被添加到无界阻塞队列workQueue中,因此线程中的线程数不会超过corePoolSize
151                             2. 由于1,使用无界队列时的 maximumPoolSize是一个无效参数
152                             3. 由于1和2,使用无界队列时的 keepAliveTime 是一个无效参数
153                             4.  不会拒绝任务
154                 CachedThreadPool
155                     根据需要创建新线程,是大小无界的线程池
156                     应用场景
157                         适用于执行很多的短期异步任务的小程序
158                         适用于负载较轻的服务器
159                     特点
160                         corePool为0,maximumPoolSize为Integer.MAX_VALUE,这就意味着所有的任务一提交就会加入到阻塞队列中。
161                         keepAliveTime这是为60L,unit设置为TimeUnit.SECONDS,意味着空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。
162                         阻塞队列采用的SynchronousQueue,每个插入操作都必须等待另一个线程对应的移除操作,此处把主线程提交的任务传递给空闲线程去执行。
163                         SynchronousQueue是一个没有元素的阻塞队列,加上corePool = 0 ,maximumPoolSize = Integer.MAX_VALUE,这样就会存在一个问题,如果主线程提交任务的速度远远大于CachedThreadPool的处理速度,则CachedThreadPool会不断地创建新线程来执行任务,这样有可能会导致系统耗尽CPU和内存资源,所以在使用该线程池是,一定要注意控制并发的任务数,否则创建大量的线程可能导致严重的性能问题。
164                     重要操作
165                         offer
166                             主线程执行offer操作与空闲线程执行的poll操作配对成功后,主线程把任务交给空闲线程执行
167                         execute
168                             执行任务
169                         poll
170                             让空闲线程在SynchronousQueue中等待60s,如果等待到新任务则执行,否则,空闲线程将终止
171         向线程池提交任务
172             execute()
173                 用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功
174                 工作原理
175                     1. 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(执行这一步骤需要获取全局锁)
176                     2. 如果运行的线程等于或多于corePoolSize(完成预热之后),则将任务加入BlockingQueue,这一步不需要全局锁
177                     3. 如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务 (执行这一步骤需要获取全局锁)
178                     4. 如果创建的线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExcution()方法
179             submit()
180                 用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过它可以判定任务是否执行成功。
181                 future.get()方法用来获取返回值,它会阻塞当前线程直到任务完成
182                 future.get(long timeout, TimeUnit unit) 方法会阻塞当前线程一段时间后立即返回,这时候可能任务没有执行完
183         关闭线程池
184             原理
185                 遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断地的任务可能永远无法终止
186             方法
187                 shutdown
188                     只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程
189                 shutdownNow
190                     首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,任务不一定要执行完
191         合理配置线程池
192             任务的性质
193                 CPU密集型任务
194                     N(cpu)+1  个线程的线程池
195                 IO密集型任务
196                     2*N(cpu) 个线程的线程池
197                 混合型任务
198                     拆分成一个CPU密集型任务和一个IO密集型任务
199             任务的优先级
200 201 202 203                 用PriorityBlockingQueue
204             任务的执行时间
205 206 207 208             任务的依赖性
209                 是否依赖其他系统资源,比如数据库连接
210                 等待返回结果的时间越长,CPU空闲时间越长,那么线程数应该设置的越大,以便更好利用线程池
211             建议使用有界队列。有界队列能够提高系统的稳定性和预警能力 ,无界队列会直接撑爆内存,导致系统不可用
212             用PriorityBlockingQueue ,让执行时间短的任务先执行
213         线程池的监控
214             方便出问题时,可以根据线程池的使用情况,快速定位问题
215             参数
216                 taskCount
217                     线程池需要执行的任务数量
218                 completedTaskCount
219                     线程池在运行过程中已经完成的任务数量
220                 largestPoolSize
221                     线程池曾将创建过的最大线程数量。如果该数字等于线程池的最大大小,表示线程池曾经满过
222                 getPoolSize
223                     线程池的线程数量
224                 getActiveCount
225                     获取活动的线程数
226             重写方法
227                 beforeExecute()
228                     任务执行前
229                 afterExecute()
230                     任务执行后
231                 terminated()
232                     线程池关闭前
233     ScheduledThreadPoolExecutor
234         可以在给定的延迟后运行命令,或者定期执行命令,与Timer类似,但比其功能更强大,更灵活
235         ScheduledThreadPoolExecutor  V.S. Timer
236             ScheduledThreadPoolExecutor可以再构造函数中指定多个对应的后台进程数
237             Timer对应的是单个后台进程
238         内部类
239             DelayedWorkQueue
240                 所使用的阻塞队列变成了DelayedWorkQueue
241                 DelayedWorkQueue为ScheduledThreadPoolExecutor中的内部类,它其实和阻塞队列DelayQueue有点儿类似
242                 DelayedWorkQueue中的任务必然是按照延迟时间从短到长来进行排序的
243                 Reentrant+Condition
244             ScheduledFutureTask
245                 待调度的任务
246                 ScheduledFutureTask内部继承FutureTask,实现RunnableScheduledFuture接口
247                 三个比较重要的变量
248                             private final long sequenceNumber;
249                         /** 任务被添加到ScheduledThreadPoolExecutor中的序号 */
250                             private long time;
251                                 /** 任务要执行的具体时间 */
252                             private final long period;
253                                 /**  任务的间隔周期 */
254                 compareTo方法,提供一个排序算法,该算法规则是:首先按照time排序,time小的排在前面,大的排在后面,如果time相同,则使用sequenceNumber排序,小的排在前面,大的排在后面。
255                 compareTo()方法使用于DelayedWorkQueue队列对其元素ScheduledThreadPoolExecutor task进行排序的算法
256         创建
257             通常使用工厂类Executors来创建
258             2种类型
259                 ScheduledThreadPoolExecutor
260                     包含固定个数个线程
261                     适用于资源管理而需要限制线程数的场景
262                 SingleThreadScheduledExecutor
263                     包含一个线程
264                     适用于单个线程,需要保证顺序执行各个任务的场景
265         4个调度器
266             schedule(Callable callable, long delay, TimeUnit unit) :创建并执行在给定延迟后启用的 ScheduledFuture。
267             schedule(Runnable command, long delay, TimeUnit unit) :创建并执行在给定延迟后启用的一次性操作。
268             scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) :创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。
269 scheduleAtFixedRate是周期固定,也就说它是不会受到这个延迟的影响的,每个线程的调度周期在初始化时就已经绝对了,是什么时候调度就是什么时候调度,它不会因为上一个线程的调度失效延迟而受到影响。
270             scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) :创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。
271 scheduleWithFixedDelay是每个线程的调度间隔固定,也就是说第一个线程与第二线程之间间隔delay,第二个与第三个间隔delay,以此类推。
272         调度和执行
273             run()
274                 1. 调用isPeriodic()获取该线程是否为周期性任务标志,然后调用canRunInCurrentRunState()方法判断该线程是否可以执行,如果不可以执行则调用cancel()取消任务。
275                 2. 如果当线程已经到达了执行点,则调用run()方法执行task,该run()方法是在FutureTask中定义的。
276                 3. 否则调用runAndReset()方法运行并重置状态,调用setNextRunTime()方法重新计算任务的下次执行时间,重新把任务添加到队列中,让该任务可以重复执行。
277                 reExecutePeriodic重要的是调用super.getQueue().add(task);将任务task加入的队列DelayedWorkQueue中
278     FutureTask
279         实现了Future接口和Runnable接口,代表异步计算的结果,
280         因为实现了Runnable接口,可以交给Executor执行,也可以由调用线程直接执行
281         重要操作
282             run()  3种状态
283                 未启动
284                 已启动
285                 已完成
286                     正常结束
287                     取消而结束
288                     异常而结束
289             get()
290                 3种状态
291                     未启动
292                         阻塞
293                             底层是 LockSupport.park();
294                     已启动
295                         阻塞
296                             底层是 LockSupport.park();
297                     已完成
298                         立即返回结果或者抛出异常
299             cannel()
300                 3种状态
301                     未启动
302                         任务不会被执行
303                     已启动
304                         cannel(true):中断执行任务的线程
305                         cannel(false):不中断执行任务的线程
306                     已完成
307                         返回false
308         应用场景
309             当一个线程需要等待另一个线程把某个任务执行完后它才能继续执行
View Code

 

7. 参考网址

  1. 参考来源:http://cmsblogs.com/wp-content/resources/img/sike-juc.png
  2. 《Java并发编程的艺术》_方腾飞PDF 提取码:o9vr
  3. http://ifeve.com/the-art-of-java-concurrency-program-1/
  4. Java并发学习系列-绪论
  5. Java并发编程实战
  6. 死磕 Java 并发精品合集
posted @ 2019-07-17 11:32  海米傻傻  阅读(3076)  评论(0编辑  收藏  举报