第9章 并发编程及线程池
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
T1,T3是多线程本身的带来的开销,希望减少T1,T3所用的时间,从而减 少T的时间。如果在程序中频繁的创建或销毁线程,这导致T1和T3在T中占 有相当比例。显然这是突出了线程的弱点(T1,T3),而不是优点(并发 性)。 线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器 程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者 一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开 销了。
见源文件:Exchanger/ExchangerTest.java
阻塞队列
阻塞队列常用于多线程编程中,由于协调线程之间的合作;当队列是空的时 ,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加 元素的操作会被阻塞。 BlockingQueue是个接口,有如下实现类: 1. ArrayBlockQueue:一个由数组支持的有界阻塞队列。此队列按 FIFO( 先进先出)原则对元素进行排序。 2. LinkedBlockQueue:一个可改变大小的阻塞队列。此队列按 FIFO(先 进先出)原则对元素进行排序。创建其对象如果没有明确大小,默认值是 Integer.MAX_VALUE。 3. PriorityBlockingQueue:类似于LinkedBlockingQueue,但其所含对 象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数所带的 Comparator决定的顺序。 4. SynchronousQueue:同步队列。同步队列没有任何容量,每个插入必 须等待另一个线程移除,反之亦然。
核心线程池对象- ThreadPoolExecutor
ThreadPoolExecutor是线程池体系中的核心类,用来创建和维护线程池对象 。主要属性如下: corePoolSize:核心线程数量(包括空闲线程) maximumPoolsize: 线程池中能创建的最大线程数 keepAliveTime: 当线程数大于核心数时, 空闲线程被销毁前等待任务的最长 时间 unitkeepAliveTime: 参数的时间单位 workQueue: 保存任务的队列,任务实现Runnable接口,由Execute方法调 用执行 threadFactory:创建线程对象所使用的工厂。 handler:由于超出线程范围和队列容量而使执行任务被阻塞时采用的处理程 序
4
线程池的工作原理
5
创建线程池
要设计一个线程池至少要考虑以下几个方面: ①对任务进行描述的类,包含线程池中线程执行的所 有信息。 ②可动态变化的、保存任务的队列。 ③线程池管理器,用来创建、销毁线程池,提供对任 务的调用与转发。 ④处理任务的工作线程类。 ⑤查询线程,用来检测任务的完成情况。 ⑥拒绝策略。
见源文件:BlockingQueue/BlockingQueueTest.java
阻塞队列
应用场景: 可以利用两个只包含一个元素的阻塞队列实现线程同步(生产者消费者)。
Queue1.put() Queue2.put()
Queue2.take() Queue1.put()
Queue1.take()
Queue2.put()
Java并发编程技术 欧阳宏基
本章内容
1.线程池的概念与工作原理 2. Executor并发编程框架以及线程池执行策略 3. Future与Callable 4. 线程锁 5. 利用Condition实现线程间通信 6. 阻塞队列
线程池的概念与工作原理
为什么使用线程池: 假设在一台服务器完成一项任务的时间为T T1 创建线程的时间 T2 在线程中执行任务的时间,包括线程间同步所需时间 T3 线程销毁的时间 那么T = T1+T2+T3。
见源文件:ThreadPoolTest.java
④newScheduledThreadPool:创建一个大小无限的线程池。 此线程池支持定时以及周期性执行任务的需求。
见源文件:ScheduledThreadPoolTest.java
Callable&Future
采用实现Runnable接口的线程无法取得返回值。 Callable是接口,其中包含一个call方法,该方法具有返回值。实现 Callable接口的类能够当做多线程中的任务类。 Future类可以拿到Callable的返回值,Future的类型应该与Callable返回值 类型一致。 见源文件:callable/CallableAndFutureTest.java
线程锁
传统实现线程同步的方法: (1) synchronized代码段 (2) synchronized方法 JDK1.5之后提供了线程锁技术来实现同步 线程锁的特点: (1) 比传统线程技术中的synchronized关键字更加面向对象,在实现互斥( 同步)的代码中,必须要获得相应的锁对象。 (2) 上锁操作的相关代码出现在资源所在类的方法中,而不是线程代码中。
利用Semaphore控制线程并发个数
Semaphore在提供线程同步的基础上,用来控制线程 并发的个数。
(1) 例如有5个座位,10个人要来坐,一次同时能容纳5个人坐, 只能等到有人离开后,其余人才能坐。坐这个座位要获得许可 权,这个许可权就是Semaphore。 (2)剩余的5个人随机获得许可权,或者按照Semaphore设定的 顺序来获得许可权。 见源文件:Semaphore/SemaphoreTest.java
屏障1
屏障2
见源文件:CyclicBarrier/ CyclicBarrierTest.java
CountDownLatchห้องสมุดไป่ตู้步工具
倒计时计数器,调用该对象的countDown()方法将技 术值减1,等到减到0为止,所有等待该计数器的线程 开始运行。 应用场景:在比赛中,所有运动员等到裁判鸣哨,然 后比赛开始,裁判等到所有运动员到终点后,比赛结 束。
见源文件:CountDownLatch/CountDownLatchTest.java
CountDownLatch同步工具
模拟抢红包程序中存在一个缺陷: 最先启动的线程抢到红包的概率是很大的,那么如何 该让所有的线程都具备抢到红包的概率呢?
解决思路:让所有线程都启动后等待一个计数器,当这个计数 器减为0时,再这些线程来执行抢红包的逻辑,这样的话所有 线程的机会都是相等的。
线程池的执行策略
Executors通过工厂方法提供了四种执行策略来管理工作线程 ,分别是: ①newSingleThreadExecutor:确保所有任务按照提交的先后 顺序由唯一工作线程来执行。 ②newFixedThreadPool:线程数量是固定的,随着任务的提 交而一一创建,直到数量达到最大值。 ③newCachedThreadPool:一个可缓存的线程池,线程池大 小完全依赖于操作系统(或者JVM)能够创建的最大线程数, 并且能根据当前任务的数量来调整线程池中线程的个数,适合 于执行生存周期较短的异步任务。
某些情况下,需要执行一组任务,哪个任务先完成,就先取哪个任务的值 。这种情况下可以使用CompletionService。 CompletionService将生产新的异步任务与使用已完成任务的结果分离开 来的服务。生产者 submit 执行的任务。使用者 take 已完成的任务,并按 照完成这些任务的顺序处理它们的结果。 见源文件:callable/CompletionServiceTest.java
见源文件:lock/LockTest.java 见源文件:lock/BonusTest.java
线程锁
锁的分类: (1) 读锁 (2) 写锁 接口ReadWriteLock 维护了一对相关的锁,一个用于只读操 作,另一个用于写入操作。如果只进行读操作,那么多个线程 可以对同一个共享资源加多次读锁。 写入锁是独占的,用在对共享资源进行修改的情况下。多个线 程要同时进行修改操作,一次只能有一个线程加写锁。在进行 写操作时,不能加读锁。
见源文件:ThreadPoolExecutorTest.java
线程池的执行策略
执行策略是一种资源管理方式,通过限制并发的数量 来确保应用程序不会由于资源耗尽而失败。 执行策略主要从执行任务的线程、任务的执行顺序、 任务并发执行的个数、任务队列中等待执行的个数、 对哪个任务进行拒绝并如何通知应用程序等方面来定 义任务的执行,从而确保应用程序具有较好的性能。
利用Condition实现线程间通信
线程间通信都是基于生产生和消费者模型的。 传统的线程间通信方式:在线程同步的基础上,不同的线程对同一个对象 加锁并执行wait()和nofify()方法。 见源文件:condition/TraditionTest.java Condition的功能类似于传统Object.wait()和Object.notify()方法, Condition对象由Lock对象的newCondition()对象创建。通过执行 signal()和await()方法在线程间通信。 见源文件:condition/ConditionTest.java Condition对象的一个优势在于能够方便实现多个线程间(线程个数大于2 个)的通信。 见源文件:condition/ThreeConditionTest.java
见源文件:lock/ReadWriteLockTest.java
基于线程锁的缓存机制
缓存是内存中的一块特殊区域,用于存放应用系统中经常会用 到的数据,用以减少数据库的查询操作以提高应用系统性能。 缓存机制普遍存在于计算机应用中,例如Hibernate框架的 Session,存在两级缓存机制。 见源文件:缓存/CacheTest.java
见源文件:BlockingQueue/BlockingQueueTest1.java
相关练习
(1)已知有16个日志对象,在一个线程中每隔1秒打 印一条日志对象,程序源码如备注所示。 要求:在源代码基础上进行修改,要求采用4个线 程在4秒中将日志对象打印完毕。
见源文件:BlockingQueue/BlockingQueueTest2.java
自定义线程池还是具有相当大难度的
JDK自带的线程池
<<interface>> Executor
<<interface>> ExecutorService
AbstractExecutorService
ScheduledExecutorService
ThreadPoolExecutor
ScheduledThreadPoolExecutor
见源文件:CountDownLatch/BonusTest1.java
Exchanger数据交换工具
Exchanger用于在两个线程之间交换数据,其中一个 线程执行一定的逻辑后需要把自己的某个数据和另一 个线程的数据进行交换,这个线程就要等待另一个线 程的到来,两者交换数据后再独自运行。 应用场景:两个人相约某个时刻到某个地点进行交易 ,两个人都到了之后,一手交钱,一手交货,然后各 忙各的了。
3
线程池的概念与组成部分
线程池:一种管理一定数量线程的手段,线程池中的线程数量 是由运行时机器容量、负载的配置信息以及动态信息决定。 应用程序在启动时创建一定数量的线程放入线程池,线程池 通过将需要并发运行的任务放入到任务队列中等待空闲线程 来处理。 组成部分: (1) 线程池管理器(ThreadPoolManager):用于创建并管理 线程池 (2) 工作线程(WorkThread): 线程池中线程 (3) 任务接口(Task):每个任务必须实现的接口,以供工作 线程调度任务的执行。 (4) 任务队列:用于存放没有处理的任务。提供一种缓冲机制。
CyclicBarrier同步工具
一个同步辅助类,它允许一组线程互相等待,直到 到达某个公共屏障点 (common barrier point)。公 共屏障点可以在线程运行过程中设置多次。 例如:大家约好7点在学校门口 集合(屏障),然后一起出发去公 园,到公园后自由活动,下午5 点在公园门口集合(屏障)返回。
见源文件:Exchanger/ExchangerTest.java
阻塞队列
阻塞队列常用于多线程编程中,由于协调线程之间的合作;当队列是空的时 ,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加 元素的操作会被阻塞。 BlockingQueue是个接口,有如下实现类: 1. ArrayBlockQueue:一个由数组支持的有界阻塞队列。此队列按 FIFO( 先进先出)原则对元素进行排序。 2. LinkedBlockQueue:一个可改变大小的阻塞队列。此队列按 FIFO(先 进先出)原则对元素进行排序。创建其对象如果没有明确大小,默认值是 Integer.MAX_VALUE。 3. PriorityBlockingQueue:类似于LinkedBlockingQueue,但其所含对 象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数所带的 Comparator决定的顺序。 4. SynchronousQueue:同步队列。同步队列没有任何容量,每个插入必 须等待另一个线程移除,反之亦然。
核心线程池对象- ThreadPoolExecutor
ThreadPoolExecutor是线程池体系中的核心类,用来创建和维护线程池对象 。主要属性如下: corePoolSize:核心线程数量(包括空闲线程) maximumPoolsize: 线程池中能创建的最大线程数 keepAliveTime: 当线程数大于核心数时, 空闲线程被销毁前等待任务的最长 时间 unitkeepAliveTime: 参数的时间单位 workQueue: 保存任务的队列,任务实现Runnable接口,由Execute方法调 用执行 threadFactory:创建线程对象所使用的工厂。 handler:由于超出线程范围和队列容量而使执行任务被阻塞时采用的处理程 序
4
线程池的工作原理
5
创建线程池
要设计一个线程池至少要考虑以下几个方面: ①对任务进行描述的类,包含线程池中线程执行的所 有信息。 ②可动态变化的、保存任务的队列。 ③线程池管理器,用来创建、销毁线程池,提供对任 务的调用与转发。 ④处理任务的工作线程类。 ⑤查询线程,用来检测任务的完成情况。 ⑥拒绝策略。
见源文件:BlockingQueue/BlockingQueueTest.java
阻塞队列
应用场景: 可以利用两个只包含一个元素的阻塞队列实现线程同步(生产者消费者)。
Queue1.put() Queue2.put()
Queue2.take() Queue1.put()
Queue1.take()
Queue2.put()
Java并发编程技术 欧阳宏基
本章内容
1.线程池的概念与工作原理 2. Executor并发编程框架以及线程池执行策略 3. Future与Callable 4. 线程锁 5. 利用Condition实现线程间通信 6. 阻塞队列
线程池的概念与工作原理
为什么使用线程池: 假设在一台服务器完成一项任务的时间为T T1 创建线程的时间 T2 在线程中执行任务的时间,包括线程间同步所需时间 T3 线程销毁的时间 那么T = T1+T2+T3。
见源文件:ThreadPoolTest.java
④newScheduledThreadPool:创建一个大小无限的线程池。 此线程池支持定时以及周期性执行任务的需求。
见源文件:ScheduledThreadPoolTest.java
Callable&Future
采用实现Runnable接口的线程无法取得返回值。 Callable是接口,其中包含一个call方法,该方法具有返回值。实现 Callable接口的类能够当做多线程中的任务类。 Future类可以拿到Callable的返回值,Future的类型应该与Callable返回值 类型一致。 见源文件:callable/CallableAndFutureTest.java
线程锁
传统实现线程同步的方法: (1) synchronized代码段 (2) synchronized方法 JDK1.5之后提供了线程锁技术来实现同步 线程锁的特点: (1) 比传统线程技术中的synchronized关键字更加面向对象,在实现互斥( 同步)的代码中,必须要获得相应的锁对象。 (2) 上锁操作的相关代码出现在资源所在类的方法中,而不是线程代码中。
利用Semaphore控制线程并发个数
Semaphore在提供线程同步的基础上,用来控制线程 并发的个数。
(1) 例如有5个座位,10个人要来坐,一次同时能容纳5个人坐, 只能等到有人离开后,其余人才能坐。坐这个座位要获得许可 权,这个许可权就是Semaphore。 (2)剩余的5个人随机获得许可权,或者按照Semaphore设定的 顺序来获得许可权。 见源文件:Semaphore/SemaphoreTest.java
屏障1
屏障2
见源文件:CyclicBarrier/ CyclicBarrierTest.java
CountDownLatchห้องสมุดไป่ตู้步工具
倒计时计数器,调用该对象的countDown()方法将技 术值减1,等到减到0为止,所有等待该计数器的线程 开始运行。 应用场景:在比赛中,所有运动员等到裁判鸣哨,然 后比赛开始,裁判等到所有运动员到终点后,比赛结 束。
见源文件:CountDownLatch/CountDownLatchTest.java
CountDownLatch同步工具
模拟抢红包程序中存在一个缺陷: 最先启动的线程抢到红包的概率是很大的,那么如何 该让所有的线程都具备抢到红包的概率呢?
解决思路:让所有线程都启动后等待一个计数器,当这个计数 器减为0时,再这些线程来执行抢红包的逻辑,这样的话所有 线程的机会都是相等的。
线程池的执行策略
Executors通过工厂方法提供了四种执行策略来管理工作线程 ,分别是: ①newSingleThreadExecutor:确保所有任务按照提交的先后 顺序由唯一工作线程来执行。 ②newFixedThreadPool:线程数量是固定的,随着任务的提 交而一一创建,直到数量达到最大值。 ③newCachedThreadPool:一个可缓存的线程池,线程池大 小完全依赖于操作系统(或者JVM)能够创建的最大线程数, 并且能根据当前任务的数量来调整线程池中线程的个数,适合 于执行生存周期较短的异步任务。
某些情况下,需要执行一组任务,哪个任务先完成,就先取哪个任务的值 。这种情况下可以使用CompletionService。 CompletionService将生产新的异步任务与使用已完成任务的结果分离开 来的服务。生产者 submit 执行的任务。使用者 take 已完成的任务,并按 照完成这些任务的顺序处理它们的结果。 见源文件:callable/CompletionServiceTest.java
见源文件:lock/LockTest.java 见源文件:lock/BonusTest.java
线程锁
锁的分类: (1) 读锁 (2) 写锁 接口ReadWriteLock 维护了一对相关的锁,一个用于只读操 作,另一个用于写入操作。如果只进行读操作,那么多个线程 可以对同一个共享资源加多次读锁。 写入锁是独占的,用在对共享资源进行修改的情况下。多个线 程要同时进行修改操作,一次只能有一个线程加写锁。在进行 写操作时,不能加读锁。
见源文件:ThreadPoolExecutorTest.java
线程池的执行策略
执行策略是一种资源管理方式,通过限制并发的数量 来确保应用程序不会由于资源耗尽而失败。 执行策略主要从执行任务的线程、任务的执行顺序、 任务并发执行的个数、任务队列中等待执行的个数、 对哪个任务进行拒绝并如何通知应用程序等方面来定 义任务的执行,从而确保应用程序具有较好的性能。
利用Condition实现线程间通信
线程间通信都是基于生产生和消费者模型的。 传统的线程间通信方式:在线程同步的基础上,不同的线程对同一个对象 加锁并执行wait()和nofify()方法。 见源文件:condition/TraditionTest.java Condition的功能类似于传统Object.wait()和Object.notify()方法, Condition对象由Lock对象的newCondition()对象创建。通过执行 signal()和await()方法在线程间通信。 见源文件:condition/ConditionTest.java Condition对象的一个优势在于能够方便实现多个线程间(线程个数大于2 个)的通信。 见源文件:condition/ThreeConditionTest.java
见源文件:lock/ReadWriteLockTest.java
基于线程锁的缓存机制
缓存是内存中的一块特殊区域,用于存放应用系统中经常会用 到的数据,用以减少数据库的查询操作以提高应用系统性能。 缓存机制普遍存在于计算机应用中,例如Hibernate框架的 Session,存在两级缓存机制。 见源文件:缓存/CacheTest.java
见源文件:BlockingQueue/BlockingQueueTest1.java
相关练习
(1)已知有16个日志对象,在一个线程中每隔1秒打 印一条日志对象,程序源码如备注所示。 要求:在源代码基础上进行修改,要求采用4个线 程在4秒中将日志对象打印完毕。
见源文件:BlockingQueue/BlockingQueueTest2.java
自定义线程池还是具有相当大难度的
JDK自带的线程池
<<interface>> Executor
<<interface>> ExecutorService
AbstractExecutorService
ScheduledExecutorService
ThreadPoolExecutor
ScheduledThreadPoolExecutor
见源文件:CountDownLatch/BonusTest1.java
Exchanger数据交换工具
Exchanger用于在两个线程之间交换数据,其中一个 线程执行一定的逻辑后需要把自己的某个数据和另一 个线程的数据进行交换,这个线程就要等待另一个线 程的到来,两者交换数据后再独自运行。 应用场景:两个人相约某个时刻到某个地点进行交易 ,两个人都到了之后,一手交钱,一手交货,然后各 忙各的了。
3
线程池的概念与组成部分
线程池:一种管理一定数量线程的手段,线程池中的线程数量 是由运行时机器容量、负载的配置信息以及动态信息决定。 应用程序在启动时创建一定数量的线程放入线程池,线程池 通过将需要并发运行的任务放入到任务队列中等待空闲线程 来处理。 组成部分: (1) 线程池管理器(ThreadPoolManager):用于创建并管理 线程池 (2) 工作线程(WorkThread): 线程池中线程 (3) 任务接口(Task):每个任务必须实现的接口,以供工作 线程调度任务的执行。 (4) 任务队列:用于存放没有处理的任务。提供一种缓冲机制。
CyclicBarrier同步工具
一个同步辅助类,它允许一组线程互相等待,直到 到达某个公共屏障点 (common barrier point)。公 共屏障点可以在线程运行过程中设置多次。 例如:大家约好7点在学校门口 集合(屏障),然后一起出发去公 园,到公园后自由活动,下午5 点在公园门口集合(屏障)返回。