
线程和多线程【精品-】.ppt
97页第第13章章 线线 程程线程和多线程线程和多线程线程的概念线程的概念®在程序开始投入运行时,系统从程序入口在程序开始投入运行时,系统从程序入口开始按开始按语句的顺序语句的顺序(其中包括其中包括顺序顺序、、分支分支和和循环循环)完成相应指令直至结尾,从出口退出,完成相应指令直至结尾,从出口退出,同时整个程序结束这样的语句结构称之同时整个程序结束这样的语句结构称之为为进程进程,或者说,或者说进程进程就是程序在处理机中就是程序在处理机中的一次运行的一次运行线程的概念线程的概念®一一个个进进程程既既包包括括其其所所要要执执行行的的指指令令,,也也包包括括了了执执行行指指令令所所需需的的任任何何系系统统资资源源,,如如CPU、、内内存存空空间间、、I/O端端口口等等,,不不同同进进程程所所占用的系统资源相对独立占用的系统资源相对独立线程的概念线程的概念®目目前前所所流流行行的的操操作作系系统统中中,,大大局局部部都都是是支支持持多多任任务务的的(如如Windows 3.X,,Windows NT,,Windows 95,,OS/2及及UNIX的的各各个个版版本本),,这这实实际际就就是是一一种种多多进进程程的的概概念念——每每一个任务就是一个进程。
一个任务就是一个进程线程的概念线程的概念®线线程程是是比比进进程程单单位位更更小小的的执执行行单单位位,,在在形形式式上上同同进进程程十十分分相相似似——都都是是用用一一个个顺顺序序执行的语句序列来完成特定的功能执行的语句序列来完成特定的功能®线线程程没没有有入入口口,,也也没没有有出出口口,,因因此此其其自自身身不不能能自自动动运运行行,,而而必必须须栖栖身身于于某某一一进进程程之之中,由进程触发执行中,由进程触发执行线程的概念线程的概念®在在系系统统资资源源的的使使用用上上,,属属于于同同一一进进程程的的所所有有线线程程共共享享该该进进程程的的系系统统资资源源,,但但是是线线程程之间切换的速度比进程切换要快得多之间切换的速度比进程切换要快得多线程的概念线程的概念®从从微观微观上讲,一个时间里只能有一个作业上讲,一个时间里只能有一个作业被执行,被执行,®在在宏观宏观上可使多个作业被同时执行,即等上可使多个作业被同时执行,即等同于要让多台计算机同时工作,使系统资同于要让多台计算机同时工作,使系统资源特别是源特别是CPU的利用率得到提高,从而提的利用率得到提高,从而提高了整个程序的执行效率。
高了整个程序的执行效率线程的概念线程的概念®为为了了到到达达多多线线程程的的效效果果,,Java语语言言把把线线程程或或执执行行环环境境〔〔execution context〕〕当当作作一一种种拥拥有有自自己己的的程程序序代代码码和和数数据据的的对对CPU的的封封装单位,由虚拟机提供控制装单位,由虚拟机提供控制®Java类类库库中中的的类类java.lang.Thread允允许许创创立立并控制所创立的线程并控制所创立的线程线程的结构线程的结构以下图是线程运行机制示意图:以下图是线程运行机制示意图:CPUCodeData线程的结构线程的结构线程包含三个主要局部线程包含三个主要局部:虚拟虚拟CPU本身,本身,CPU执行的代码,执行的代码,代码操作的数据代码操作的数据线程的结构线程的结构®在在Java中,虚拟中,虚拟CPU表达于表达于Thread类中®当当一一个个线线程程被被构构造造时时,,它它由由构构造造方方法法参参数数、、执行代码、操作数据来初始化执行代码、操作数据来初始化创立线程一创立线程一——继承继承Thread类类®将一个类定义为将一个类定义为Thread的子类,那么这个类就可的子类,那么这个类就可以用来表示线程。
以用来表示线程®应用这种形式的构造方法创立线程对象时不用给应用这种形式的构造方法创立线程对象时不用给出任何参数出任何参数®这个类中有一个至关重要的方法这个类中有一个至关重要的方法——public void run(),这个方法称为线程体,它是整个线程的核心,,这个方法称为线程体,它是整个线程的核心,线程所要完成任务的代码都定义程体中,实线程所要完成任务的代码都定义程体中,实际上不同功能的线程之间的区别就在于它们线程际上不同功能的线程之间的区别就在于它们线程体的不同体的不同程序程序13-1public class myThread extends Thread{public void run(){while(running){// 执行假设干操作执行假设干操作sleep(100);}}public static void main(String args[]){Thread t = new myThread();// 执行假设干操作执行假设干操作}}创立线程二创立线程二——实现实现Runnable接口接口®Runnable是是Java中中用用以以实实现现线线程程的的接接口口,,从从根根本本上上讲讲,,任任何何实实现现线线程程功功能能的的类类都都必必须实现该接口。
须实现该接口®Runnable接接口口中中只只定定义义了了一一个个方方法法就就是是run()方法,也就是线程体方法,也就是线程体创立线程二创立线程二——实现实现Runnable接口接口®Thread第第 二二 种种 构构 造造 方方 法法 中中 包包 含含 有有 一一 个个Runnable实实例例的的参参数数,,这这就就是是说说,,必必须须定定义义一一个个实实现现Runnable接接口口的的类类并并产产生生一一个个该该类类的的实实例例,,对对该该实实例例的的引引用用就就是是适适合合于于这个构造方法的参数这个构造方法的参数程序程序 13-2public class xyz implements Runnable{int i;public void run(){while (true) {System.out.println("Hello "+i++);}}}程序程序 13-2可以构造一个线程如下:可以构造一个线程如下:Runnable r = new xyz();Thread t = new Thread(r);线程运行环境线程运行环境CPUCodeDataThread txyz rclass xyz线程线程关于两种创立线程方法的讨论关于两种创立线程方法的讨论1. 适用于采用实现适用于采用实现Runnable接口方法的情况接口方法的情况因因为为Java只只允允许许单单继继承承,,如如果果一一个个类类已已经经继继承承了了Thread,就不能再继承其他类。
就不能再继承其他类比比方方对对于于Applet程程序序,,由由于于必必须须继继承承了了java. applet.Applet,,因因此此就就只只能能采采取取这这种种实实现现接接口口的的方方法由由于于某某些些原原因因而而几几次次被被迫迫采采用用实实现现Runnable接接口口的的方方法法,,可可能能会会出出于于保保持持程程序序风风格格的的一一贯贯性性而而继继续续使使用这种方法用这种方法关于两种创立线程方法的讨论关于两种创立线程方法的讨论2. 适用于采用继承适用于采用继承Thread方法的情况方法的情况当一个当一个run()方法置于方法置于Thread类的子类中类的子类中时,时,this实际上引用的是控制当前运行系实际上引用的是控制当前运行系统统 的的Thread实例,所以,代码不必写实例,所以,代码不必写 得得繁琐:繁琐:Thread.currentThread().suspend();可简单地写为:可简单地写为:suspend();线程的启动线程的启动®必须通过方法必须通过方法start()来启动线程,来启动线程,start()方方法也在法也在Thread类中线程的调度线程的调度®在一台只具有一个在一台只具有一个CPU的机器上,的机器上,CPU在在同一时间只能分配给一个线程做一件事。
同一时间只能分配给一个线程做一件事®当当有有多多于于一一个个的的线线程程工工作作时时,,在在Java中中,,线程调度通常是线程调度通常是抢占式抢占式,而不是时间片式而不是时间片式®抢抢占占式式调调度度是是指指可可能能有有多多个个线线程程准准备备运运行行,,但只有一个在真正运行但只有一个在真正运行线程的调度线程的调度®一个线程获得执行权,这个线程将持续运一个线程获得执行权,这个线程将持续运行下去,直到它运行结束或因为某种原因行下去,直到它运行结束或因为某种原因而阻塞,而阻塞,®或者有另一个高优先级线程就绪〔这种情或者有另一个高优先级线程就绪〔这种情况称为低优先级线程被高优先级线程所抢况称为低优先级线程被高优先级线程所抢占〕线程的调度线程的调度一个线程被阻塞的原因:一个线程被阻塞的原因:因为执行了因为执行了Thread.sleep()调用,成心让它调用,成心让它暂停一段时间;暂停一段时间;因为需要等待一个较慢的外部设备,例如磁因为需要等待一个较慢的外部设备,例如磁盘或用户盘或用户线程的调度线程的调度®所有被阻塞的线程按次序排列,组成一个所有被阻塞的线程按次序排列,组成一个阻塞队列阻塞队列。
®所有就绪但没有运行的线程那么根据其优所有就绪但没有运行的线程那么根据其优先级排入一个就绪队列先级排入一个就绪队列线程的调度线程的调度®当当CPU空闲时,如果就绪队列不空,就绪空闲时,如果就绪队列不空,就绪队列中第一个具有最高优先级的线程将运队列中第一个具有最高优先级的线程将运行®当一个线程被抢占而停止运行时,它的运当一个线程被抢占而停止运行时,它的运行态被改变并放到就绪队列的队尾;行态被改变并放到就绪队列的队尾;线程的调度线程的调度®一个被阻塞〔可能因为睡眠或等待一个被阻塞〔可能因为睡眠或等待I/O设备设备〕的线程就绪后通常也放到就绪队列的队〕的线程就绪后通常也放到就绪队列的队尾程序 13-3public class xyz implements Runnable{public void run(){while(true){…… …… // 执行假设干操作执行假设干操作// 给其他线程运行的时机给其他线程运行的时机try{Thread.sleep(10);}catch(InterruptedException e){// 该线程为其他线程所中断该线程为其他线程所中断}}}}程序程序13-3®sleep()是是类类Thread中中的的静静态态方方法法,,因因此此可可以通过以通过Thread.sleep(x)直接引用。
直接引用®参参数数x指指定定了了线线程程在在再再次次启启动动前前必必须须休休眠眠的的最小时间,是以毫秒为单位的最小时间,是以毫秒为单位的®同同 时时 该该 方方 法法 可可 能能 引引 发发 中中 断断 异异 常常InterruptedException,,因因此此要要进进行行捕捕获获和和处理程序程序13-3®除除sleep()方方法法以以外外,,类类Thread中中的的另另一一个个方方法法yield()可可以以给给其其他他同同等等优优先先级级线线程程一一个个运运行的时机行的时机®如如果果在在就就绪绪队队列列中中有有其其他他同同优优先先级级的的线线程程,,yield()把把调调用用者者放放入入就就绪绪队队列列尾尾,,并并允允许许其其他他线线程程运运行行;;如如果果没没有有这这样样的的线线程程,,那那么么yield()不做任何工作不做任何工作程序程序13-3®sleep()调调用用允允许许低低优优先先级级进进程程运运行行,,而而yield()方法只给同优先级进程以运行时机方法只给同优先级进程以运行时机线程的根本控制线程的根本控制结束线程结束线程®当当一一个个线线程程从从run()方方法法的的结结尾尾处处返返回回时时,,它它自自动动消消亡亡并并不不能能再再被被运运行行,,可可以以将将其其理理解解为为自自然然死死亡亡;®利利用用stop()方方法法强强制制停停止止,,可可以以将将其其理理解解为为强强迫迫死亡,这种方法必须用于死亡,这种方法必须用于Thread类的特定实例类的特定实例中。
中程序程序 13-4public class xyz implements Runnable{…… ……// 执行线程的主要操作执行线程的主要操作}public class ThreadTest{public static void main(String args[]){Runnable r = new xyz();Threadt = new Thread(r);t.start();// 进行其他操作进行其他操作if (time_to_kill){t.stop();}}}程序程序 13-5®可以利用可以利用Thread类中的静态方法类中的静态方法currentThread()来引用正在运行的线程来引用正在运行的线程public class xyz implements Runnable{public void run(){while(true){…… …… // 执行线程的主要操作执行线程的主要操作if (time_to_die){ Thread.currentThread().stop();}}}}检查线程检查线程®可以利用方法可以利用方法isAlive()来获取一个线程的活来获取一个线程的活动状态。
动状态®活动状态不意味着这个线程正在执行,而活动状态不意味着这个线程正在执行,而只说明这个线程已被启动,并且既没有运只说明这个线程已被启动,并且既没有运行行stop(),也尚未运行完方法,也尚未运行完方法run()挂起线程挂起线程®有几种方法可以用来暂停一个线程的运行有几种方法可以用来暂停一个线程的运行在挂起之后,必须在挂起之后,必须重新唤醒重新唤醒线程进入运行线程进入运行挂起线程的方法挂起线程的方法1. sleep()它用于暂时停止一个线程的执行它用于暂时停止一个线程的执行线程不是休眠期满后就立刻被唤醒线程不是休眠期满后就立刻被唤醒重新调度只在以下几种情况下才会发生:重新调度只在以下几种情况下才会发生: 被唤醒的线程具有更高的优先级被唤醒的线程具有更高的优先级 正在执行的线程因为其他原因被阻塞正在执行的线程因为其他原因被阻塞 程序处于支持时间片的系统中程序处于支持时间片的系统中挂起线程的方法挂起线程的方法2. suspend()和和resume()强制挂起线程,而不指定休眠时间,由其他线强制挂起线程,而不指定休眠时间,由其他线程负责唤醒其继续执行程负责唤醒其继续执行。
线程中有一对方法用于完成此功能,这就是线程中有一对方法用于完成此功能,这就是suspend()和和resume()程序13-6class xyz implements Runnable{public void run(){…… …… // 执行线程的主要操作执行线程的主要操作// 暂停线程运行暂停线程运行Thread.currnetThread().suspend();………… // 继续运行继续运行}}程序程序 13-6class Usexyz{public static void main(String args[]){Runnable r = new xyz();Thread t = new Thread(r);t.start();/* 暂停当前线程运行,以使暂停当前线程运行,以使xyz的实例得以运行的实例得以运行*/Thread.sleep(1000);/* xyz实例被实例被suspend()方法暂停,将控制权返还给主方法暂停,将控制权返还给主线程,并由主线程重新唤醒线程线程,并由主线程重新唤醒线程t*/t.resume();Thread.yield();}}程序13-6线程线程t在运行到在运行到suspend()以后被强制挂起,暂停运以后被强制挂起,暂停运行,直到主线程调用行,直到主线程调用t.resume()时才被重新唤醒。
时才被重新唤醒一个线程可以被任何一条语句代码所挂起,只要一个线程可以被任何一条语句代码所挂起,只要它具有该线程的操作权,即引用它的变量一个它具有该线程的操作权,即引用它的变量一个线程只能被不同于它自身的线程所唤醒线程只能被不同于它自身的线程所唤醒挂起线程的方法挂起线程的方法3. join()方法方法join()将引起现行线程等待,直至方法将引起现行线程等待,直至方法join所所调用的线程结束调用的线程结束程序程序13-7public void timeout(){// 暂停该线程,等候其他线程暂停该线程,等候其他线程(tt)结束结束tt.join();// 其他线程结束后,继续进行该线程其他线程结束后,继续进行该线程…… ………… ……}程序13-7说明®这样,在执行方法这样,在执行方法timeout()以后,现行的线以后,现行的线程将被阻塞,直到程将被阻塞,直到tt运行结束运行结束®join()方法在调用时也可以使用一个以毫秒方法在调用时也可以使用一个以毫秒计的时间值:计的时间值:void join(long timeout);®此时此时join方法将挂起现行线程方法将挂起现行线程timeout毫秒,毫秒,或直到调用的线程结束,实际挂起时间以或直到调用的线程结束,实际挂起时间以二者中时间较少的为准。
二者中时间较少的为准同步问题同步问题class Stack{int idx = 0;char data[] = new char[6];public void push(char c){data[idx]=c;idx ++;}public char pop(){idx --;return data[idx];}}问题的提出问题的提出®现现在在设设想想有有两两个个线线程程都都具具有有对对这这个个类类的的同同一一个个对对象象的的引引用用,,一一个个线线程程正正在在把把数数据据推推入入栈栈中中,,而而另另一一个个与与这这个个线线程程独独立立的的线线程程,,正在弹出栈中元素正在弹出栈中元素问题的提出问题的提出®问题:假设线程问题:假设线程a负责参加字符,线程负责参加字符,线程b负负责移出字符线程责移出字符线程a刚刚参加了一个字符,刚刚参加了一个字符,但是尚未递增索引值,由于某种原因,恰但是尚未递增索引值,由于某种原因,恰恰这时它被抢占了那么此时该对象代表恰这时它被抢占了那么此时该对象代表的数据模式将出现错误的数据模式将出现错误buffer | p | q | r | | | |idx=2^问题的提出问题的提出®如果线程如果线程a被及时唤醒,还没有什么危险,被及时唤醒,还没有什么危险,但是如果此时线程但是如果此时线程b正在等待移出一个字符,正在等待移出一个字符,当线程当线程a处于等待状态时,线程处于等待状态时,线程b就得到了就得到了运行时机。
这样,在进入方法运行时机这样,在进入方法pop()时,数时,数据状态已经是错误的据状态已经是错误的pop()方法将继续递方法将继续递减索引值:减索引值:buffer | p | q | r | | | |®idx=1 ^问题的提出问题的提出如果线程如果线程a继续运行将得到什么结果:继续运行将得到什么结果:线程线程a从从push()方法中被打断的地方继续运行,方法中被打断的地方继续运行,递增了索引值,因此有:递增了索引值,因此有:buffer | p | q | r | | | |inx=2^将再也读不到字母将再也读不到字母“r〞了问题的提出问题的提出®可以选择的一种解决方法是禁止线程可以选择的一种解决方法是禁止线程a在完在完成代码关键局部时被切换成代码关键局部时被切换®另一种方法,也是另一种方法,也是Java采用的方法,就是采用的方法,就是提供一个特殊的锁定标志来处理数据提供一个特殊的锁定标志来处理数据对象的锁定标志对象的锁定标志®Java可可以以为为每每一一个个对对象象的的实实例例配配有有一一个个标标志,这个标志称做志,这个标志称做“锁定标志〞。
锁定标志〞®关关键键字字synchronized提提供供了了操操作作这这个个标标志志的的方法程序程序 13-9class stack{int idx = 0;char data[ ] = new char[6];public void push(char c){synchronized (this){data[idx]=c;idx ++;}}.……}程序程序13-10public char pop(){synchronized (this){idx --;return data[idx];}}对象的锁定标志以下图是线程锁定标志使用示意图:线程线程1...因等待同步资源而挂起的线程队列因等待同步资源而挂起的线程队列对象的锁定标志对象的锁定标志®当当持持有有锁锁定定标标志志的的线线程程运运行行完完synchronized()调调用用包含的程序块后,这个标志将会被自动返还包含的程序块后,这个标志将会被自动返还®Java保保证证了了该该标标志志通通常常能能够够被被正正确确地地返返还还,,即即使使被被同同步步的的程程序序块块产产生生了了一一个个异异常常,,或或者者某某个个循循环环中断跳出了该程序块,这个标志也能被正确返还。
中断跳出了该程序块,这个标志也能被正确返还对象的锁定标志对象的锁定标志®如果一个线程两次调用了同一个对象,在如果一个线程两次调用了同一个对象,在退出最外层后这个标志也将被正确释放,退出最外层后这个标志也将被正确释放,而在退出内层时那么不会执行释放这些而在退出内层时那么不会执行释放这些规那么使得同步程序块的使用比其他系统规那么使得同步程序块的使用比其他系统中等同的操作,如信号灯的管理,要简单中等同的操作,如信号灯的管理,要简单得多同步方法同步方法synchronized()语句的标准写法为:语句的标准写法为:public void push(char c){synchronized(this){…… …………}}同步方法同步方法®synchronized()语句的参数必须是语句的参数必须是this®Java语言允许使用下面这种简洁的写法:语言允许使用下面这种简洁的写法:public synchronized void push(char c){…………}同步方法同步方法区别:区别:如果把如果把synchronized用做方法的修饰字,那用做方法的修饰字,那么整个方法都将称为同步块,这可能会使么整个方法都将称为同步块,这可能会使持有锁定标记的时间比实际需要的时间要持有锁定标记的时间比实际需要的时间要长,从而降低效率。
长,从而降低效率使用前一种方法来标记可以提醒用户同步在使用前一种方法来标记可以提醒用户同步在发生,这在防止死锁时非常重要发生,这在防止死锁时非常重要死锁死锁®在多线程竞争使用多资源的程序中,有可在多线程竞争使用多资源的程序中,有可能出现死锁的情况能出现死锁的情况®这种情况发生在一个线程等待另一个线程这种情况发生在一个线程等待另一个线程所持有的锁,而那个线程又在等待第一个所持有的锁,而那个线程又在等待第一个线程持有的锁的时候线程持有的锁的时候死锁死锁®每个线程都不能继续运行,除非另一线程每个线程都不能继续运行,除非另一线程运行完同步程序块运行完同步程序块®因为哪个线程都不能继续运行,所以哪个因为哪个线程都不能继续运行,所以哪个线程都无法运行完同步程序块线程都无法运行完同步程序块死锁死锁A.monitorThread xSynchronizedA.methoda()SynchronizedA.methodb()B.monitorThread ySynchronizedB.methoda()SynchronizedB.methodb()locked程序程序13-11class classA {public classB b;synchronized void methoda() {String name = Thread.currentThread().getName();System.out.println( name +“entered classA.methoda." );try {Thread.sleep( 1000 );} catch ( InterruptedException e ){ }System.out.println( name +" trying to call classB.methodb()" );b.methodb();}synchronized void methodb() {System.out.println( " inside classB.mothedb() " );}}class classB {public classA a;synchronized void methoda() {String name = Thread.currentThread().getName();System.out.println( name +" entered classB.methoda." );try {Thread.sleep( 1000 );} catch ( InterruptedException e ){ }System.out.println( name +" trying to call classA.methodb()" );a.methodb();}synchronized void methodb() {System.out.println( " inside classB.mothedb() " );}}class DeadLock implements Runnable{classA a = new classA();classB b = new classB();DeadLock(){Thread.currentThread().setName( "MainThread" );a.b = b;b.a = a;new Thread( this ).start();a.methoda();System.out.println( "back to main thread" );}public void run(){Thread.currentThread().setName( "RacingThread" );b.methoda();System.out.println( "back to racing thread" );}public static void main( String args[ ] ){new DeadLock();}}程序程序 13-11®如果运行该程序就会发现在出现上述信息如果运行该程序就会发现在出现上述信息后发生死锁的情况。
后发生死锁的情况®Java既不监测也不采取方法防止这种状态,既不监测也不采取方法防止这种状态,因此保证死锁状态不会发生就成了程序员因此保证死锁状态不会发生就成了程序员的职责死锁死锁一个防止死锁发生的方法是:一个防止死锁发生的方法是:如果有多个对象要被同步,对于获得这些锁如果有多个对象要被同步,对于获得这些锁的顺序作一个综合决定,并在整个程序中的顺序作一个综合决定,并在整个程序中遵循这个顺序遵循这个顺序线程交互线程交互——wait()和和notify()®多个线程常常被创立用来完成不相关的任多个线程常常被创立用来完成不相关的任务,而线程之间有一些交互务,而线程之间有一些交互问题的提出问题的提出®为什么两个线程可能需要交互呢?为什么两个线程可能需要交互呢?简单的例子:简单的例子:有两个人,一个在刷盘子,有两个人,一个在刷盘子,另一个在烘干这两个人各自代表一个线另一个在烘干这两个人各自代表一个线程,他们之间有一个共享的对象程,他们之间有一个共享的对象——碗橱每个人都各司其职,显然,碗橱上有刷好每个人都各司其职,显然,碗橱上有刷好的盘子时,烘干的人才能开始工作;而如的盘子时,烘干的人才能开始工作;而如果刷盘子的人刷得太快,刷好的盘子占满果刷盘子的人刷得太快,刷好的盘子占满了碗橱时,他就不能再继续工作了,而要了碗橱时,他就不能再继续工作了,而要等到碗橱上有空位置才行。
等到碗橱上有空位置才行解决方法解决方法®Java提供了一种建立在对象实例之上的交提供了一种建立在对象实例之上的交互方法®Java中的每个对象实例都有两个线程队列中的每个对象实例都有两个线程队列和它相连和它相连®第一个用来排列等待锁定标志的线程第一个用来排列等待锁定标志的线程®第二个那么用来实现第二个那么用来实现wait()和和notify()的交的交互机制解决方法解决方法类类java.lang.Object中定义了三个方法中定义了三个方法:wait(),,notify() ,,notifyAll(),,解决方法解决方法wait()和和notify()线程线程a代表刷盘子,线程代表刷盘子,线程b代表烘干,它们都代表烘干,它们都有对对象有对对象drainingBoard的访问权的访问权假设线程假设线程b——烘干线程,想要进行烘干工作,烘干线程,想要进行烘干工作,而此时碗橱是空的,那么应表示如下:而此时碗橱是空的,那么应表示如下: if (drainingBoard.isEmpty()) drainingBoard.wait();解决方法解决方法®当线程当线程b执行了执行了wait()调用后,它不可再执调用后,它不可再执行,并参加到对象行,并参加到对象drainingBorad的等待队的等待队列中。
在有线程将它从这个队列中释放之列中在有线程将它从这个队列中释放之前,它不能再次运行前,它不能再次运行解决方法解决方法烘干线程怎样才能重新运行呢?烘干线程怎样才能重新运行呢?®这应该由洗刷线程来通知它已经有工作可这应该由洗刷线程来通知它已经有工作可以做了,运行以做了,运行drainningBoard的的notify()调调用可以做到这一点:用可以做到这一点:drainingBoard.addItem(plate);drainingBoard.notify();®此时,此时,drainingBoard的等待队列中第一个的等待队列中第一个阻塞线程由队列中释放出来,并可重新参阻塞线程由队列中释放出来,并可重新参加运行的竞争加运行的竞争解决方法解决方法®如果等待队列中没有阻塞线程时调用了方法如果等待队列中没有阻塞线程时调用了方法notify(),那么这个调用不做任何工作那么这个调用不做任何工作notify()调用不调用不会被保存到以后再发生效用会被保存到以后再发生效用®方法方法notify()最多只能释放等待队列中的第一个线最多只能释放等待队列中的第一个线程,如果有多个线程在等待,那么其他的线程将程,如果有多个线程在等待,那么其他的线程将继续留在队列中。
继续留在队列中®方法方法notifyAll()能够在程序设计需要时释放所有等能够在程序设计需要时释放所有等待线程解决方法解决方法®Java中的实现并不像这里所假设的这样简单特中的实现并不像这里所假设的这样简单特别是,等待队列本身构成了一个特殊的数据结构,别是,等待队列本身构成了一个特殊的数据结构,需要使用同步机制加以保护需要使用同步机制加以保护®在调用一个对象的在调用一个对象的wait(),,notify()或或notifyAll()时,时,必须首先持有该对象的必须首先持有该对象的锁定标志锁定标志,因此这些方法,因此这些方法必须在同步程序块中调用必须在同步程序块中调用解决方法解决方法®将代码改变如下:将代码改变如下:synchronized(drainingBoard){if (drainingBoard.isEmpty())drainingBoard.wait();}解决方法解决方法®同样有:同样有:synchronized(drainingBoard){drainingBoard.addItem(plate);drainingBoard.notify();}解决方法解决方法®另外一个有趣的问题:另外一个有趣的问题:就是线程执行被同步的语就是线程执行被同步的语句时需要拥有对象的锁定标志,实际的实现过程句时需要拥有对象的锁定标志,实际的实现过程中是不会出现这种情况的。
中是不会出现这种情况的®Java将首先把锁定标志返回给对象,因此即使一将首先把锁定标志返回给对象,因此即使一个线程由于执行个线程由于执行wait()调用而被阻塞,它也不会影调用而被阻塞,它也不会影响其他等待锁定标志的线程的运行响其他等待锁定标志的线程的运行解决方法解决方法®当一个线程被当一个线程被notify()后,它并不立即变为后,它并不立即变为可执行状态,而仅仅是从等待队列中移入可执行状态,而仅仅是从等待队列中移入锁定标志队列中这样,在重新获得锁定锁定标志队列中这样,在重新获得锁定标志之前,它仍旧不能继续运行标志之前,它仍旧不能继续运行®另一方面,在实际实现中,方法另一方面,在实际实现中,方法wait()既可既可以被以被notify()终止,也可以通过调用线程的终止,也可以通过调用线程的interrupt()方法来终止后一种情况下,方法来终止后一种情况下,wait()会抛会抛出一个出一个InterruptedException异常,所以异常,所以需要把它放在需要把它放在try/catch结构中解决方法解决方法®线程状态及状态转换示意图线程状态及状态转换示意图:BlockedDeadNewbornRunnableRunningstop()start()stop()yield()stop()stop()resume()notify()综合应用实例综合应用实例®现在来建立一个经典的生产者现在来建立一个经典的生产者/消费者问题消费者问题的实际例子。
的实际例子®提供以下两个接口:提供以下两个接口:®public void push(char c);®public char pop();综合应用实例综合应用实例®首先来看生产者生产者将随机产生首先来看生产者生产者将随机产生20个个大写字母并将它们推入栈中,每次推入动大写字母并将它们推入栈中,每次推入动作之间有一个随机的延时,延时的范围为作之间有一个随机的延时,延时的范围为0~100ms每个推入的字母将在屏幕上显每个推入的字母将在屏幕上显示生产者线程生产者线程public void run(){char c;for (int i = 0; i < 20; i++){c = (char)(Math.random()*26+’A’);theStack.push(c);System.out.println("Produced: "+c);try{Thread.sleep((int)(Math.random()*100));}catch (InterruptedException e){ }}}消费者线程消费者线程®消费者从栈中取出消费者从栈中取出20个字母,每次取出动个字母,每次取出动作之间有延时,这里的延时为作之间有延时,这里的延时为0~~2s。
这意这意味着栈的清空比填入要慢,因此栈能够很味着栈的清空比填入要慢,因此栈能够很快被完全填满快被完全填满消费者线程消费者线程public void run(){char c;for (int i = 0; i < 20; i++){c = theStack.pop();System.out.println("Consuned: "+c);try{ Thread.sleep((int)(Math.random()*1000));}catch (InterruptedException e){ }}}说明说明®栈需要一个索引值和一个缓冲区数组,因栈需要一个索引值和一个缓冲区数组,因为本例是要演示缓冲区满时正确的操作和为本例是要演示缓冲区满时正确的操作和同步方法,所以缓冲区仅设为能容纳同步方法,所以缓冲区仅设为能容纳6个字个字母一个新构造的母一个新构造的SyncStack应该为空应该为空构造类构造类class SyncStack{private int index = 0;private char buffer[ ] = new char[6];public synchronized char pop(){ }public synchronized void push(char c){ }}推入和弹出方法public synchronized void push(char c){while(index == buffer.length){try{this.wait();}catch(InterruptedException e){ }}this.notify();buffer [index] = c;index++;}public synchronized char pop(){while(index == 0){try{this.wait();}catch(InterruptedException e){ }}this.notify();index--;return buffer[index];}程序程序13-17//最终的程序代码最终的程序代码: SyncTest.javapackage modl3;public class SyncTest{public static void main(String args[]){SyncStack stack = new SyncStack();Runnable source = new Producer(stack);Runnable sink= new Consumer(stack);Thread t1 = new Thread(source);Thread t2 = new Thread(sink);t1.start();t2.start();}}// Producer.javapackage modl3;public class Producer implements Runnable{SyncStack theStack;public Producer(SyncStack s){theStack = s;}public void run(){char c;for (int i = 0; i < 20; i++){c = (char)(Math.random()*26+ ‘ A’);theStack.push(c);System.out.println("Produced: "+c);try{Thread.sleep((int)(Math.random()*100));}catch (InterruptedException e){ }}}// Consumer.javapackage modl3;public class Consumer implements Runnable{SyncStack theStack;public Consumer(SyncStack s){theStack = s;}public void run(){char c;for (int i = 0; i < 20; i++){c = theStack.pop();System.out.println("Consuned: "+c);try{ Thread.sleep((int)(Math.random()*1000));}catch (InterruptedException e){ } } }}// SyncStack.javapackage modl3;public class SyncStack{private int index = 0;private char buffer[ ] = new char[6];public synchronized void push(char c){while(index == buffer.length){try{this.wait();}catch(InterruptedException e){ }}this.notify();buffer[index] = c;index++;}public synchronized char pop(){while(index == 0){try{this.wait();}catch(InterruptedException e){ }}this.notify();index--;return buffer [index];}}USQNLJHFDBzxvsqomkigec97531+)*%!YWUSQOMKIGDBzxvtrpnkigeca8642+)*%!ZXVTQOMKIGECAyvtrpnljhfda86420-(&!ZXVTRPNLJGECAywusqnljhfdb97520-(&$#YWURPNLJHFDBywusqomkigdb97531+)&$#YWUSQOMJHFDBzxLJHFCAywusqomkhfdb97531-(&$#YWUSQNLJHFDBzxusqomkigec97531+)*%!YWUSQOMKIFDBzxvtrpnkigeca8641+)*%!ZXVTQOMKIGECAyvtrpnljhfca86420-(&!ZXVTRPNLIGECAywusqnljhfdb97520-(&$#YWTRPNLJHFDBywusqomkifdb97531+)&$#YWUSxvtrpnljhfca86420-(&!ZXVTRPNLIGECAywusqnljhfdb97420-(&$#YWTRPNLJHFDBywusqomkifdb97531+)&$#YWUSQOLJHFDBzxvtqomkigeca8531+)*%!ZWUSQOMKIGEBzxvtrpnligeca86420)*%!ZXVTRPMKIGECAywtrpnljhfdb86420-(&$ZXV+)*%!ZXVTQOMKIGECAxvtrpnljhfca86420-(%!ZXVTRPNLIGECAywuspnljhfdb97420-(&$#YWTRPNLJHFDAywusqomkifdb97531+(&$#YWUSQOLJHFDBzxvtqomkigeca7531+)*%!ZWUSQOMKIGDBzxvtrpnligeca86420)*%!ZXVTROMKIGECAywtrpnl1+(&$#YWUSQOLJHFDBzxvsqomkigeca7531+)*%!ZWUSQOMKIGDBzxvtrpnligeca8642+)*%!ZXVTROMKIGECAywtrpnljhfda86420-(&$ZXVTRPNLJGECAywusqoljhfdb97530-(&$#YWURPNLJHFDBzwusqomkigdb97531+)*$#YWUSQOMKHFTRPNLJHFDAywusqomkhfdb97531+(&$#YWUSQOLJHFDBzxvsqomkigeca7531+)*%!YWUSQOMKIGDBzxvtrpnligeca8642+)*%!ZXVTROMKIGECAyvtrpnljhfda86420-(&$ZXVTRPNLJGECAywusqoljhfdb97520-(&$IGDBzxvtrpnkigeca8642+)*%!ZXVTROMKIGECAyvtrpnljhfda86420-(&!ZXVTRPNLJGECAywusqolxvtrpnljheca86420-(%!ZXVTRPNKIGECAywuspnljhfdb96420-(&$#YVTRPNLJHFDAywusGECAywtrpnljhfda86420-(&$ZXVTRPNLJGECAywusqoljhfdb97530-(&$#YWURPNLJHFDBzwusqomkigdb97531+)*$#YWUSQOMKHFDBzxvtromkigeca8631+)*%!ZXUSQOMKIGECzxvtrpnljheca86420-*%!ZXVTRPNKIGECAywurpnljhfd$#YWUSQOMJHFDBzxvtromkigeca8631+)*%!ZXUSQOMKIGECzxvtrpnljgeca86420-*%!ZXVTRPNKIGECAywurpnljhfdb96420-(&$#XVTRPNLJHFCAywusqomkhfdb97531-(&$#YWUSQNLJHFDBzxusGECAywusqoljhfdb97520-(&$#YWURPNLJHFDBywusqomkigdb97531+)*$#YWUSQOMJHFDBzxvtromkigeca8531+)*%!ZXUSQOMKIGECzxvtrpnljgeca86420-*%!ZXVTRPMKIGECAywurpnljhfdb96420-OMJHFDBzxvtromkigeca8531+)*%!ZXUSQOMKIGEBzxvtrpnzxvsqomkigec97531+)*%!YWUSQOMKIGDBzxvtrpnkigeca8642+)*%!ZXVTQOMKIGECAyvtrpnljhfda86420-(&!ZXVTRPNLZXUSQOMKIGECzxvtrpnljgeca86420-*%!ZXVTRPNKIGECAywurpnljhfdb96420-(&$#XVTRPNLJHFCAywusqomkhfdb97531-(&$#YWUSQNLJHFDBzxusqomkigec97531+)*%!YWUSQOMKIFDBzxvtrpnkigeca8641RPNLJHFCAywusqomjhfdb97531-(&$#YWUSQNLJHFDBzxusqomkigec97531+)*%#YWUSQOMKIFDBzxvtrpnkigeca8641+)*%!ZXVTQOMKIGECAxvtrpnljhfca86420-(&!ZXVTRPNLIGECAywusqnljhvtrpnljgeca86420-*%!ZXVTRPMKIGECAywurpnljhfdb864geca8642+)*%!ZXVTQOMKIGECAyvtrpnljhfda86420-(&!ZXVTRPNLJGECAywusqnljhfdb97520-(&$#YWURPNLJHFDBywusqomkigdb97531+)&$#YWUSQOMJHFDBzxva86420-(&!ZXVTRPNLJGECAywusqnljhfdb97520-(&$#YWTRPNLJHFDBywusqomkigdb97531+)&$#YWUSQOMJHFDBzxvtqomkigeca8531+)*%!ZXUSQOMKIGEBzxvtrpnljgeca86420)*%!Z31+)*%#YWUSQOMKIFDBzxvtrpnkigeca8641+)*%!ZXVTQOMKIGECAxvtrpnljhfca86420-(&!ZXVTRPNLIGECAywusqnljhfdb97420-(&$#YBzxvtrpnkywusqomkigdb97531+)*$#YWUSQOMJHFDBzxvtromkigeca8531+)*%!ZXUSQOMKIGECzxvtrpnljgeca86420-*%!ZXVTRPMKIGECAywurpnljhfdb96420-(&$#XVTRPNLJHFCAywusqomjhfdb975jhfca86420-(&!ZXVTRPNLJGECAywusqnljhfdb97520-(&$#YWTRPNLJHFDBywusqomkigdb97531+)&$#YWUSQOMJHFDBzxvtqomkigeca8531+)*%!ZXUSQOMKIGEBzxvtrpnljgeca#YWTRPNLJHFDBywusqomkifdb97531+)&$#YWUS&$#YVTRPNLJHFCAywusqomkhfdb97531-(&$#YWUSQNLJHFDBzxvsqomkigec97531+)*%!YWUSQOMKIFDBzxvtrpnkigeca8641+)*LJHFCAywusqomkhfdb97531-(&$#YWUSQNLJHFDBzxusqomkigec97531+)*%#YWUSQOMKIFDBzxvtrpnkigeca8641+)*%!ZXVTQOMKIGECAxvtrpnljhfca86420-(&!ZXVTRPNLZXUSQOMKIGEBzxvtrpnljgeca86420-*%!ZXVTRPMKIGECAywurpnljhfdb86420-(&$#X+)*%!ZXVTQOMKIGECAyvtrpnljhfda86420-(&!ZXVTRPNLJGECAgec97531+)*%!YWUSQOMKIFDBzxvtrpnkigeca8642+)*%!ZXVTQOMKIGECAyvtrpnljhfca86420-(&!ZXVTRPNLJGECAywusqnljhfdb97520-(&$#YWTRPNLJHFDBywusqomkigdrpnljhfdb96420-(&$#XVTRPNLJHFCAywusqomkhfdb97531-(&$#YWUSQNLJHFDBzxusqomkigec97531+)*%!YWUSQ$#YWURPNLJHFDBzwusqomkigdb97531+)*$#YWUSQOMJHFDBhfda86420-(&!ZXVTRPNLJGECAywusqoljhfdb97520-(&$#YWURPNLJHFDBywusqomkigdb97531+)*$#YWUSQOMJHFDBzxvtromkigeca8531+)*%!ZXUSQOMKIGECzxvJHFDBzxvsqomkigec97531+)*%!YWUSQOMKIGDBzxvtrpnkigeca8642+)*%!ZXVTQOMKIGECAyvtrpnljhf$#YWUSQNLJHFDBzxvsqomkigec97531+)*%!YWUSQOMKIGDBzxvtrpnkigeca86kigdb97531+)*$#YWUSQOMKHFDBzxvtromkigeca8631+)*%!ZXUSQOMKIGECzxvtrpnljheca86420-*%!ZXVTRPNKIGECAywurpnljhfdb96420-(&$#YVTRPNLJXVTROMKIGECAyvtrpnljhfda86420-(&!ZXVTRPNLJGECAywusqoljhf%!YWUSQOMKIGDBzxvtrpnligeca8642+)*%!ZXVTROMKIGECAyvtrpnljhfda86420-(&$ZXVTRPNLJGECAywusqoljhvtrpnljheca86420-(%!ZXVTRPNKIGECAywuspnljhfdb96420-(&$#YVTRPNLJHFDAywusqomkhfdb97531+(&$#YWUSQNLJHFDBzxvsqomkigeca7531+)*%!GECAywuspnljhfdb96Bzwusqomkig*%!ZXVTROMKIGECAywtrpnljhfda86420-(&$ZXVTRPNLJHECAywusqoljhfdb97530-(&$#YWURPNLJHFDBzwusqomkigeb97531+)*$#YWUSQOMKHFDBzxvtHFDAywusqomkifdb97531+(&$#YWUSQOLJHFDBzxvsqomkigeca7531+)*%!YWUSQOMKIGDBzxvtrpnligeca8642PNLJHFDAywusqomkifdb97531+(&$#YWUSQOLJHFDBzxvsqomkigeca753hfdb97530-(&$#YWUSPNLJHFDBzwusqomkigeb97531+)*%#YWUSQOMKHFDBzxvtrpmkigeca8631+)*%!ZXVSQOMKIGECAxvtHFDBzxvtqomkigeca7531+)*%!ZWUSQOMKIGDBzxvtrpnligeca86420NLJHFDAywusqomkifdb97531+)&$#YWUSQOLJHFDBzxvtqomkigeca7531+)*%!ZWUSQOMKIGEBzxvtrpnligeca86kigeb97531+)*%#YWUSQOMKIFDBzxvtrpmkigeca8641+)*%!ZXVSQOMKIGECAxvtrpnljhfca86420-(%!ZXVTRPNLIGECAywuspnljhfdb97。












