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