Java语言程序设计(自考)课件 第十章 多线程_第1页
Java语言程序设计(自考)课件 第十章 多线程_第2页
Java语言程序设计(自考)课件 第十章 多线程_第3页
Java语言程序设计(自考)课件 第十章 多线程_第4页
Java语言程序设计(自考)课件 第十章 多线程_第5页
已阅读5页,还剩54页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第十章多线程

学习目标能够叙述线程和多线程的概念,线程各种状态之间转换的条件,线程的优先级能够使用Thread类和Runnabe接口创建线程能够对线程进行控制,实现线程之间的互斥和同步本章主要内容线程和多线程12线程的基本控制3创建线程线程的互斥4线程的同步5线程的概念程序是一段静态的代码,它是应用程序执行的蓝本一个进程既包括其所要执行的指令,也包括了执行指令所需的任何系统资源,如CPU、内存空间、I/O端口等,不同进程所占用的系统资源相对独立线程是进程执行过程中产生的多条执行线索,是比进程单位更小的执行单位线程的结构在Java中,线程由3部分组成:虚拟CPU,封装在java.lang.Thread类中,它控制着整个线程的运行执行的代码,传递给Thread类,由Thread类控制按序执行处理的数据,传递给Thread类,是在代码执行过程中所要处理的数据当一个线程被构造时,它由构造方法参数、执行代码、操作数据来初始化一个线程所执行的代码与其他线程可以相同也可以不同一个线程访问的数据与其他线程可以相同也可以不同多线程的优势使用多线程可以在线程间直接共享数据和资源,而多进程之间不能做到这一点使用多线程适合于开发有多种交互接口的程序多线程的机制可以减轻编写交互频繁、涉及面多的程序的困难,如侦听网络端口的程序线程的状态线程一共有4种状态,分别是新建、可运行状态、死亡及阻塞新建线程对象刚刚创建,还没有启动,此时还处于不可运行状态此时刚创建的线程处于新建状态,但已有了相应的内存空间以及其它资源可运行状态此时的线程已经启动,线程可能正在运行,也可能没有运行,只要CPU一空闲,马上就会运行可以运行但没在运行的线程都排在一个队列中,这个队列称为就绪队列可运行状态中,正在运行的线程处于运行状态,等待运行的线程处于就绪状态一般地,单CPU情况下,最多只有一个线程处于运行状态,可能会有多个线程处于就绪状态调用线程的start()方法可使线程处于“可运行”状态死亡线程死亡的原因有两个一是run()方法中最后一个语句执行完毕二是当线程遇到异常退出时便进入了死亡状态阻塞一个正在执行的线程因特殊原因,被暂停执行,就进入阻塞状态阻塞时线程不能进入就绪队列排队,必须等到引起阻塞的原因消除,才可重新进入队列排队引起阻塞的原因很多,不同原因要用不同的方法解除sleep()和wait()是两个常用的引起阻塞的方法中断线程在程序中常常调用interrupt()来终止线程interrupt()不仅可中断正在运行的线程,而且也能中断处于blocked状态的线程interrupt()会抛出InterruptedException异常测试线程是否被中断的方法voidinterrupt()向一个线程发送一个中断请求,同时把这个线程的“interrupted”状态置为true。若该线程处于“blocked”状态,会抛出InterruptedException异常staticbooleaninterrupted()检测当前线程是否已被中断,并重置状态“interrupted”值。即如果连续两次调用该方法,则第二次调用将返回falsebooleanisInterrupted()检测当前线程是否已被中断,不改变状态“interrupted”值第二节创建线程创建线程有两种方法继承Thread类创建线程实现Runnable接口创建线程继承Thread类java.lang.Thread是Java中用来表示线程的类如果将一个类定义为Thread的子类,那么这个类的对象就可以用来表示线程类Thread中典型的构造方法Thread(ThreadGroupgroup,Runnabletarget,Stringname)name作为新线程的名称,且是线程组group中的一员target必须实现接口Runnable,它是另一个线程对象创建线程的过程从Thread类派生出一个子类,在类中实现run()classLefthandextendsThread{ publicvoidrun(){……} //线程体}然后用该类创建一个对象Lefthandleft=newLefthand();用start()方法启动线程left.start();程序10.1实现Runnable接口创建线程Runnable是Java中用以实现线程的接口,从任何实现线程功能的类都必须实现该接口Thread类实际上就是因为实现了Runnable接口,所以它的子类才相应具有线程功能Runnable接口中只定义了一个方法就是run()方法,也就是线程体用Runnable()接口实现多线程时,也必须实现run()方法,也需要使用start()启动线程编写线程体publicclassxyzimplementsRunnable{ inti; publicvoidrun(){ while(true){ System.out.println("Hello"+i++); } }}利用类xyz可以构造一个线程Runnabler=newxyz();Threadt=newThread(r);程序10.2创建线程两种方法的适用条件适用于采用实现Runnable接口方法的情况因为Java只允许单继承,如果一个类已经继承了Thread,就不能再继承其他类,在一些情况下,这就被迫采用实现Runnable的方法另外,由于原来的线程采用的是实现Runnable接口的方法,可能会出于保持程序风格的一贯性而继续使用这种方法创建线程两种方法的适用条件适用于采用继承Thread方法的情况当一个run()方法置于Thread类的子类中时,this实际上引用的是控制当前运行系统的Thread实例,所以,代码不必写得像下面这样繁琐:Thread.currentThread().getState();而可简单地写为:getState();可以直接调用Thread类中的方法,代码直观,所以许多Java程序员愿意使用继承Thread的方法第三节线程的基本控制线程的启动要使线程真正在Java环境中运行,必须通过方法start()来启动start()方法也在Thread类中线程的操作方法start()启动线程对象,让线程从新建状态转为就绪状态run()用来定义线程对象被调度之后所执行的操作,用户必须重写run()方法yield()强制终止线程的执行isAlive()测试当前线程是否在活动sleep(intmillsecond)使线程休眠一段时间,时间长短由millsecond决定,单位为毫秒voidwait()使线程处于等待状态线程的调度虽然就绪线程已经可以运行,但这并不意味着这个线程一定能够立刻运行CPU在同一时间只能分配给一个线程做一件事那么当有多于一个的线程工作时,CPU是如何分配的?这就是线程的调度问题在Java中,线程调度通常是抢占式,而不是时间片式抢占式调度是指可能有多个线程准备运行,但只有一个在真正运行一个线程获得执行权,这个线程将持续运行下去,直到它运行结束或因为某种原因而阻塞,再或者有另一个高优先级线程就绪,最后一种情况称为低优先级线程被高优先级线程所抢占线程调度采用的优先级策略优先级高的先执行,优先级低的后执行每个线程创建时都会被自动分配一个优先级,默认时,继承其父类的优先级任务紧急的线程,其优先级较高同优先级的线程按“先进先出”的调度原则与线程优先级有关的静态量MAX_PRIORITY:最高优先级,值为10MIN_PRIORITY:最低优先级,值为1NORM_PRIORITY:默认优先级,值为5有关优先级的常用方法voidsetPriority(intnewPriority)重置线程优先级intgetPriority()获得当前线程的优先级staticvoidyield()暂停当前正在执行的线程,即让当前线程放弃执行权线程被阻塞的原因可能是因为执行了Thread.sleep()调用,故意让它暂停一段时间也可能是因为需要等待一个较慢的外部设备例如磁盘或用户操作的键盘线程队列所有被阻塞的线程按次序排列,组成一个阻塞队列所有就绪但没有运行的线程则根据其优先级排入一个就绪队列当CPU空闲时,如果就绪队列不空,队列中第一个具有最高优先级的线程将运行当一个线程被抢占而停止运行时,它的运行态被改变并放到就绪队列的队尾同样,一个被阻塞的线程就绪后通常也放到就绪队列的队尾结束线程当一个线程从run()方法的结尾处返回时,它自动消亡并且不能再被运行,可以将其理解为自然死亡另一种情况是遇到异常使得线程结束,可以将其理解为强迫死亡还可以使用interrupt()方法中断线程的执行查询线程状态在程序代码中,可以利用Thread类中的静态方法currentThread()来引用正在运行的线程有时候可能不知道一个线程的运行状态,这时可以使用方法isAlive()来获取一个线程是否还在活动状态的信息活动状态不意味着这个线程正在执行,而只说明这个线程已被启动挂起线程暂停一个线程也称为挂起在挂起之后,必须重新唤醒线程进入运行挂起线程的方法——sleep()方法sleep()用于暂时停止一个线程的执行挂起唤醒线程不是休眠期满后就立刻被唤醒,因为此时其他线程可能正在执行重新调度只在以下几种情况下才会发生被唤醒的线程具有更高的优先级正在执行的线程因为其他原因被阻塞程序处于支持时间片的系统中挂起线程的方法wait()和notify()/notifyAll()方法wait()方法导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法,才能唤醒线程join()方法join()将引起现行线程等待,直至方法join()所调用的线程结束比如在线程B中调用了线程A的join()方法,直到线程A执行完毕后,才会继续执行线程B可以想像成将线程A加入到当前线程B中第四节线程的互斥一些同时运行的线程需要共享数据此时,每个线程就必须要考虑与它一起共享数据的其他线程的状态与行为,否则的话就不能保证共享数据的一致性,因而也就不能保证程序的正确性栈的示例栈具有“后进先出”模式,它使用下标值idx表示栈中下一个放置元素的位置有两个独立的线程A和B都具有对这个类的同一个对象的引用,线程A负责入栈,线程B负责出栈要求线程A放入栈中的数据都要由线程B读出,不重不漏假设此时栈中已经有字符1和2,当前线程A要入栈一个字符3,调用push(3),执行了语句data[idx]=c;后被其他线程抢占了,此时尚未执行idx++语句。故idx指向最后入栈的字符的下标data123idx=2^如果此时线程A马上被唤醒,可以继续修正idx的值,从而完成一次完整的入栈操作如若不然,入栈操作执行了一半若恰巧线程B此时正占有CPU,调用pop(),执行出栈操作,则它返回的字符是2,因为它先执行idx--语句,idx的值变为1,返回的是data[1]处的字符,即字符2字符3被漏掉了多线程访问共享数据时通常会引起问题产生这种问题的原因是对共享资源访问的不完整性需要寻找一种机制来保证对共享数据操作的完整性这种完整性称为共享数据操作的同步共享数据叫做条件变量锁定标志可以禁止线程在完成代码关键部分时被切换这个关键代码部分,对于线程A就是入栈操作及下标值增加这两个动作,对于线程B就是下标值递减及出栈操作这两个动作它们要么一起完成,要么都不执行在Java中,提供了一个特殊的锁定标志来处理共享数据的访问对象的锁定标志引入“对象互斥锁”的概念,也称为监视器使用它来实现不同线程对共享数据操作的同步“对象互斥锁”阻止多个线程同时访问同一个条件变量Java可以为每一个对象的实例配有一个“对象互斥锁”实现“对象互斥锁”的方法用关键字synchronized来声明一个方法或一段代码块,该方法或代码块在执行时会获取对象实例的内置锁,其他线程必须等待锁的释放才能访问该方法或代码块中的共享数据使用关键字volatile来声明一个共享数据(变量)。但是,使用volatile关键字只能保证数据的可见性和有序性,无法实现对共享数据的原子操作,因此不能完全替代使用synchronized实现同步锁定标志示例classstack{ intidx=0; chardata[]=newchar[6]; publicvoidpush(charc){ synchronized(this){//增加同步标志 data[idx]=c; idx++; } }}锁定标志方法publiccharpop(){ synchronized(this){ //增加同步标志 idx--; returndata[idx]; }}synchronized(this)现在pop()和push()操作的部分增加了一个对synchronized(this)的调用,在第一个线程拥有锁定标记时,如果另一个线程企图执行synchronized(this)中的语句时,它将从对象this中索取锁定标记因为这个标记不可得,故该线程不能继续执行实际上这个线程将加入一个等待队列,该等待队列与对象锁定标志相连,当标志被返还给对象时,等待标志的第一个线程将得到该标志并继续运行因为等待一个对象的锁定标志的线程要等到持有该标志的线程将其返还后才能继续运行,所以在不使用该标志时将其返还就显得十分重要了事实上,当持有锁定标志的线程运行完synchronized()调用包含的程序块后,这个标志将会被自动返还如果一个线程两次调用了同一个对象,在退出最外层后这个标志也将被正确释放,而在退出内层时则不会执行释放用synchronized标识的代码段或方法即为“对象互斥锁”锁住的部分如果一个程序内有两个或以上的方法使用synchronized标志,则它们在同一个“对象互斥锁”管理之下synchronized()语句publicvoidpush(charc){ synchronized(this){

┊ }}或是publicsynchronizedvoidpush(charc){

┊}第五节线程的同步为了完成多个任务,常创建多个线程,它们可能毫不相关,但有时它们完成的任务在某种程度上有一定的关系,此时就需要线程之间有一些交互使用方法wait()和notify()/notifyall()实现线程的交互操作系统中的生产者消费者问题,就是一个经典的同步问题若共享对象中只能存放一个数据,可能出现以下问题:生产者比消费者快时,消费者会漏掉一些数据没有取到消费者比生产者快时,消费者取相同的数据解决方法Java中的每个对象实例都有两个线程队列和它相连第一个用来排列等待锁定标志的线程第二个则用来实现wait()和notify()的交互机制类java.lang.Object中定义了三个方法wait()notify()notifyAll()wait()方法导致当前的线程等待,它的作用是让当前线程释放其所持有的“对象互斥锁”,进入wait队列(等待队列)notify()/notifyAll()方法的作用是唤醒一个或所有正在等待队列中等待的线程,并将它(们)移入等待同一个“对象互斥锁”的队列notify()/notifyAll()方法和wait()方法都只能在被声明为synchronized的方法或代码段中调用方法

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论