Java并发编程实战-并发调度模式框架

Java并发编程实战-并发调度模式框架

加油站:抱怨是最没有营养的一件事.

前言:

选择串行的方式执行任务,串行处理机制通常无法提高高吞吐率和快速响应性,于是我们可以显式地为任务创建线程,为每一个请求创建一个线程来执行任务,这样可以实现更高的响应性。
但是这样会带来很多问题:

  1. 线程的创建和销毁开销非常高
  2. 活跃的线程会消耗系统资源,尤其是内存。如果可运行的线程数量多于可用处理器的数量,那么有些线程就会闲置。大量空闲的线程会占用许多内存,给GC带来很大的压力,而且大量线程在竞争CPU资源时还会产生其他性能开销。
  3. 可创建线程的数量存在一个限制。这个限制值受多个平台制约,包括JVM的启动参数,Thread构造函数中请求栈的大小以及操作系统的限制。
    总得来说,增加线程可以提高系统的吞吐率但是如果超出了这个范围,再创建更多的线程只会降低程序的速度,更严重会导致奔溃。

正文:

一: 使用Executor框架:

对于线程和任务,任务是一组逻辑工作单元,而线程是使任务异步执行的机制,Executor框架可以将线程和任务协调起来。

Executor基于生产者—消费者模型,它提供了一个标准的方法,将任务的提交和执行过程解耦,用Runnable来表示任务。提交任务的操作相当于生产者,执行任务的线程相当于消费者。
Executor有两种实现方式:

  1. 每线程每任务:
    Java并发编程实战-并发调度模式框架_第1张图片
    2.一个线程所有任务:
    Java并发编程实战-并发调度模式框架_第2张图片

二:使用线程池:

线程池的优势:
通过重用现有的线程而不是创建新线程,可以减少创建和销毁线程的开销.
当请求到来时,由于线程已经存在,可以减少等待时间,从而提高了响应性.

可以通过调用Executors中的静态工厂方法之一来创建一个线程池:
常用线程池说明:

  1. newSingleThreadExecutor
    创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
    如下图源码:
    在这里插入图片描述
  2. newFixedThreadPool
    创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
    如下图源码:
    在这里插入图片描述
  3. newCachedThreadPool
    创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
    那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
    如下图源码:
    在这里插入图片描述
    4.newScheduledThreadPool
    创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
    如下图源码:
    在这里插入图片描述
    具体参数含义:
    corePoolSize:池中所保存的线程数,包括空闲线程
    maximumPoolSize:线程池允许的最大线程数量
    keepAliveTime:存活时间,当线程数大于核心线程数时,多出来的线程为空余线程,当空余线程在一定的时间内没有新任务到达执行,则终止该线程
    unit :keepAliveTime 参数的时间单位
    workQueue :执行前用于保持任务的队列。

三: Executor的生命周期:ExecutorService

从上面所述,我们可以看出Executor通常是创建线程来执行任务,为了解决执行服务的生命周期问题,Executor扩展了ExecutorService接口,添加了生命周期的管理方法,具体看下图源码:
Java并发编程实战-并发调度模式框架_第3张图片
ExecutorService有三种状态:
running(运行), shuting down(关闭), terminated(已终止)。
shuting down(关闭)状态:
shutdown:将停止接受新的任务, 同时等待已经提交的任务完成, 包括尚未完成的任务.
terminated(已终止)状态:
等所有任务都完成之后,进入terminated状态, 可以调用awaitTermination等待ExecutorService到达终止状态, 也可以轮询检查isTerminated判断是否终止. 通常shutdown会紧随awaitTermination之后, 这样可以产生同步地关闭ExecutorService的效果.

四: 延迟任务和周期任务

延迟任务:在100ms后执行任务.
周期任务:没100ms执行一次任务.
一般来说,Timer类用于执行延迟任务和周期任务,但是Timer有以下两个问题:

  1. 只会创建一个线程来执行所有task, 如果一个task非常耗时, 会导致其他的task的实效准确性出问题
  2. Timer线程并不捕获异常,对于一些未检查异常(RuntimeException)抛出,Timer线程会被终止

五: 饱和策略

当有界队列被填满后,饱和策略就游泳了。ThreadPoolExecutor的饱和策略可以通过调用setRejectedExecutionHandler来修改。JDK提供了几种不同的实现,每一种实现有不同的饱和策略:
具体说明:
中止策略(AbortPolicy):默认的饱和策略,会抛出RejectedExecutionException: 调用者可以捕获这个隐藏然后编写满足自己需求的处理代码
抛弃策略(DiscardPolicy):当最新提交的任务不能进入队列等待执行时, 遗弃(discard)策略会默认放弃这个任务.
遗弃最旧策略(DiscardOldestPolicy):选择丢弃的任务是本应该接下来就应该执行的任务, 该策略还会尝试去重新提交新任务。(该策略最好不要和优先级队列一起使用)
调用者运行策略(CallerRunsPolicy):既不会丢弃哪个任务, 也不会抛出任何异常. 它会把一些任务退回到调用者那里, 从此缓解新任务流. 他不会在池线程中执行最新提交的任务, 但是他会在一个调用了execute的线程中执行。当工作队列充满后, 并没有预置的饱和策略来阻塞execute,当工作队列充满后,并没有预置的饱和策略来阻塞execute.但是,使用Semaphore信号量可以实现这个效果.

结尾:

随着日益增长的互联网需求,现在在各大互联网公司中,高并发处理已经是一个非常常见的问题,因此对于java程序员来说,系统掌握并发编程的能力与实战技巧无论对于实际工作应用还是面试来讲都很有必要,而线程池作为并发编程中的基础第一步,上诉多为理论知识,后续源码会上传,感谢您的关注,关注微信公众号:十点攀程 ,精彩持续进行中…
Java并发编程实战-并发调度模式框架_第4张图片

你可能感兴趣的:(java工具类)