




已阅读5页,还剩10页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第8章 多线程8.1 线程的基本概念要深入了解线程,先要弄清楚程序、进程与线程3个相互关联的基本概念。l 程序:程序是一段代码,是计算机执行的蓝本。编写程序就是希望计算机按程序蓝本执行。l 进程:进程是程序的一次执行过程,从代码加载、执行,直至完成的一个完整的过程。这个过程也是进程从产生、运行至消亡的过程。程序与进程之间的关系如同乐谱和一次演奏的关系。乐谱好比程序,演奏好比进程,演奏的依据是乐谱进程执行的依据是程序。l 线程:线程是一个控制流,也是一个执行过程,但执行单位比进程小。一个进程在其执行过程中,可以产生多个线程,形成多条执行线索。每条线索,即每个线程也有它自身的产生、运行和消亡的过程。如果把进程比作一次乐曲的演奏,线程可以比作每个乐师的奏乐。从外面看,一场音乐会给人们一次美好的音乐享受;从内部看,每个乐师正在按要求认真工作。乐师在工作时,相互之间有协调和配合。线程与进程比较,它们的共同点,都是程序的一个执行过程。不同点是进程是一个实体,每个进程有自己的状态、专用数据段(独立内存资源);同一个进程下的线程则共享进程的数据段。创建进程时,必须建立其专用数据段;创建线程时不必建立新的数据段。线程不是能够独立运行的程序,而只是某个进程内的一个执行流。线程的建立和线程间的切换速度大大超过进程,不需要数据段的保护和恢复。同时,又具备进程的大多数优点,所以线程的执行效率比进程的执行效率高。缺点是由于多个线程共享数据段,带来数据访问中的相斥和同步问题,使系统管理变得复杂。多线程在提高输入/输出设备平行工作能力、有效利用系统资源、改善计算机通信及发挥硬件的多处理器功能等方面有很多的优势。8.1.1 线程的生命周期一个线程“创建工作死亡”的过程称为线程的生命周期。线程生命周期共有五个状态:新建状态、就绪状态、运行状态、阻塞状态和死亡状态。1. 新建状态新建状态是指创建了一个线程,但它们还没有启动。处于新建状态的线程对象,只能够被启动或者终止。例如,以下代码使线程myThread处于新建状态:Thread myThread=new Thread();2. 就绪状态就绪状态是当线程处于新建状态后,调用了start()方法,线程就处于就绪状态。就绪状态线程具备了运行条件,但尚未进入运行状态。处于就绪状态的线程可以多个,这些就绪状态的线程将在就绪队列中排队,等待CPU资源。就绪状态的线程通过线程调度获得CPU资源变成运行状态。例如,以下代码使myThread处于就绪状态。myThread.start();3. 运行状态运行状态是某个就绪状态的线程获得CPU资源,正在运行。如果有更高优先级的线程进入就绪状态,则该线程将被迫放弃对CPU的控制器,加入就绪状态。使用yield()方法可以使线程主动放弃CPU。线程也可能由于结束或执行stop()方法进入死亡状态。每个线程对象都有一个run()方法,当线程对象开始执行时,系统就调用对象的run()方法。4. 阻塞状态柱塞状态是正在运行的线程遇到某个特殊情况。例如,延迟、挂起、等待I/O操作完成等,进入柱塞状态的线程让出CPU,并暂时停止自己的执行。线程进入柱塞状态后,就一直等待,直到引起柱塞的原因被消失,线程有转入就是状态,重新进入就绪列队排列。当线程再次变成运行状态时,将从暂停处开始继续运行。线程从柱塞状态恢复到就绪状态,有三种途径:(1) 自动恢复:(2) 用resume()方法恢复:(3) 用notif()或nitifyAll()方法通知恢复。也可能因为别的线程强制某个处于阻塞状态的线程终止,该线程就从阻塞状态进入死亡状态。5. 死亡状态死亡状态是指线程不再具有继续运行的能力,也不能再转到其他状态。一般有两种情况使一个线程终止,进入死亡状态。(1) 线程完成了全部工作,即执行完run()方法的最后一条语句。(2) 线程被提前强制性终止。图8.1是线程的生命周期图,图中给出从一种状态转变成另一种状态的各种可能的原因。新建就绪延迟死亡等待阻塞Start运行dispatch(assign a processorquantumexpirationyieldInterruptI/O completionSleep Interval expiresNodify或nodifyallissue I/Ocompletesleepwait图8.1 线程的生命周期图8.1.2 线程调度与优先级Java提供一个线程调度器来监视和控制就绪状态的线程。线程的调度策略采用抢占式,优先级高的线程比优先级低的线程优先执行。在优先级相同的情况下,就按“先到先服务”的原则。线程的优先级用数值表示,数值越大优先级越高(范围110)。每个线程根据继承特性自动从父进程获得一个线程的优先级,也可在程序中重新设置。对于任务较紧急的重要线程,可安排较高的优先级。相反,则给一个较低的优先级。每个Java程序都有一个默认的主线程,就是通过JVM(Java Virtual Machine,Java虚拟机)启动的第一个线程。对于应用程序,主线程执行的是main()方法。对于Applet,主线程是指浏览器加载并执行小应用程序的哪一个线程。子程序是由应用程序创建的线程。另有一种线程称为守护线程(Daemon),这是一种用于监视其他线程工作的服务线程,它的优先级最低。8.2 Thread类和Runnable接口Java程序实现多线程应用有两种途径:l 一是继承Thread类声明Thread子类,用Thread子类创建线程对象。l 二是在类中实现Runnable接口,在类中提供Runnable接口的run()方法。无论用哪种方法,都需要Java基础类库中的Thread类及其方法的支持。程序员能控制的关键性工作有两个方面:l 一是编写线程的run()方法;l 二是建立线程实例。8.2.1 Thread类Thread类是用来创建线程和提供线程操作的类。1. Thread类提供的方法Thread类为创建线程和线程控制提供以下常用的方法:(1) Thread(),创建一个线程。线程名按创建顺序为Thread_1、Thread_2等。(2) Thread(String m),创建一个以m命名的线程。(3) Thread(Runnable target),创建线程,参数target是创建线程的目标。目标是一个对象,对象的类要实现Runnable接口,类中给出接口的run()方法。(4) public Thread(Runnable target,String m),创建线程,参数target是创建线程的目标。m是线程名。(5) Thread(ThreadGroup g,String m),创建一个以m命名的线程,该线程属于指定的线程组g。(6) Thread(ThreadGroup g,Runnable target),创建一个属于指定线程组g的线程,target是线程的目标。(7) Thread(ThreadGroup g,Runnable target,String m),创建一个线程,名为m,属于指定线程组g,target是线程的目标。(8) getPriority(),获得线程的优先级。(9) set Priority(int p),设定线程的优先级为p。线程创建时,子线程继承父线程的优先级。优先级的数值越大优先级越高(缺省是5)。常用以下3个优先级:l Thread.MIN_PRIORITY(最低)l Thread.MAX_PRIORITY(最高)l Thread.NORM_PRIORITY(标准)(A) start(),启动线程,让线程从新建状态到就绪状态。(B) run(),实现线程行为(操作)的方法。(C) sleep(int dt)让线程休眠dt时间,单位是毫秒。例如代码 sleep(100);让线程休眠100毫秒时间段。在以后的这个时间段中,其他线程就会有机会被执行。当休眠时间一到,将重新到就绪队列排序。由于sleep()方法可能会产生Interrupt Expiration异常,应将sleep方法写在try块中,并用catch块处理异常。sleep()方法是static方法,不可重载。(D) currentThread(),获得当前正在占有CPU的能个线程。(E) getName(),获取线程的名字。(F) setName(),设置线程的名字。(G) isAlive(),返回boolean类型值,查询线程是否还活跃。(H) destroy(),强制线程生命周期结束。(I) stop(),强制线程生命周期结束,并完成一些清理工作,及抛出异常。有时,小应用程序的界面已从屏幕消失,但并没有停止线程的执行,会一直到浏览器关闭才结束。因此要在小应用程序的stop()方法中终止线程的执行。(J) suspend(),挂起线程,处于不可运行的阻塞状态。(K) resume(),恢复挂起的线程,重新进入就绪队列排队。(L) yield(),暂停当前正在执行的线程,若就绪队列中有与当前线程同优先级的线程,则当前线程让出CPU控制权,移到就绪队列的队尾。若队列中没有同优先级或更高优先级的线程,则当前线程继续执行。2. 用Thread子类实现多线程用Thread子类实现多线程,得先声明一个Thread类的子类,并在子类中重新定义run()方法。当程序需要建立线程时,就可以创建Thread子类的实例,并让创建的线程调用start()方法,这时,run()方法将自动执行。【例8.1】应用程序用Thread子类实现多线程。在main()方法中创建了两个线程threadA和threadB,它们分别是Thread子类threadA和threadB的实例,并分别调用它们的start()方法启动它们。threadA先调用start()方法,类threadA中的run()方法将自动执行。由于threadA线程先执行,它的run()方法输出“我是threadA”和当时的时间后,threadA线程主动休息2000毫秒,让出CPU。这时正在等待CUP的threadB线程获得CPU资源,执行它的run()方法,输出“我是threadB”和当时的时间,threadB线程主动让出CPU,1000毫秒后又来排队等待CPU资源。过了1000毫秒后,这时threadA线程还没有“醒了”,因此有轮到threadB执行。再次输出“我是threadB”import java.util.Date;public class Example8_1 static Athread threadA;static Bthread threadB;public static void main(String args)threadA=new Athread();threadB=new Bthread();threadA.start();threadB.start();class Athread extends Threadpublic void run()Date timeNew;/为了能输出当时的时间for(int i=0;i=5;i+)timeNew=new Date();System.out.println(我是threadA:+timeNew.toString();trysleep(2000);catch(InterruptedException e)class Bthread extends Threadpublic void run()Date timeNew;/为了能输出当时的时间for(int i=0;i=5;i+)timeNew=new Date();System.out.println(我是threadB:+timeNew.toString();trysleep(1000);catch(InterruptedException e)以下是例8.1程序执行的输出结果,从输出清单中可发现两线程的输出频度的差异。我是threadB:Thu Feb 16 17:26:06 CST 2012我是threadA:Thu Feb 16 17:26:06 CST 2012我是threadB:Thu Feb 16 17:26:07 CST 2012我是threadB:Thu Feb 16 17:26:08 CST 2012我是threadA:Thu Feb 16 17:26:08 CST 2012我是threadB:Thu Feb 16 17:26:09 CST 2012我是threadB:Thu Feb 16 17:26:10 CST 2012我是threadA:Thu Feb 16 17:26:10 CST 2012我是threadB:Thu Feb 16 17:26:11 CST 2012我是threadA:Thu Feb 16 17:26:12 CST 2012我是threadA:Thu Feb 16 17:26:14 CST 2012我是threadA:Thu Feb 16 17:26:16 CST 20128.2.2 Runnable接口Java.lang.Runnable接口,只有run()方法需要实现。一个实现Runnable接口的类数据上定义了一个在主线程之外的新线程的操作。用Runnable接口实现多线程的主要工作是:声明实现Runnable接口的类,在类内实现run()方法;并在类内声明线程对象,在init()方法或start()方法中创建新线程,并在start()方法中启动新线程。【例8.2】小应用程序通过Runnable接口创建线程。在类的start()方法中用构造方法Thread(this)创建了一个新的线程。this代表着该类的对象作为新线程的目标,因此类必须为这个新创建的线程实现Runnable接口,即提供run()方法。在start()方法中构造了一个名为myThread的线程,并调用Thread类的start()方法来启动这个程序。这样,run()方法被执行,除小应用程序的主线程外,又开始了一个新线程myThread。在例子中,run()方法在睡眠1秒后,调用repaint()方法重绘Applet窗口。在paint()方法中,在文本区输出线程睡眠的次数,并用随机产生的颜色和半径涂一个圆块。import java.applet.*;import java.awt.*;import javax.swing.*;public class Example8_2 extends Applet implements RunnableThread myThread=null;/声明一个线程对象JTextArea t;int k;public void start()t=new JTextArea(20,20);add(t);k=0;setSize(500,400);if(myThread=null)/重新进入小程序时,再次创建线程myThreadmyThread=new Thread(this);/创建新线程myThread.start();/启动新线程public void run()while(myThread!=null)trymyThread.sleep(1000);k+;catch(InterruptedException e)repaint();public void paint(Graphics g)double i=Math.random();if(i=360) redSeta=0;x=(int)(80.0*Math.cos(3.1415926/180.0*redSeta);y=(int)(80.0*Math.sin(3.1415926/180.0*redSeta);redPen.setColor(Color.red);/用底色画图,擦除原先所画原点redPen.fillOval(100+x, 100+y, 10, 10);tryredBall.sleep(20);catch(InterruptedException e)else if(Thread.currentThread()=blueBall)x=(int)(80.0*Math.cos(3.1415926/180.0*blueSeta);y=(int)(80.0*Math.sin(3.1415926/180.0*blueSeta);bluePen.setColor(Color.gray); /用底色画图,擦除原先所画原点bluePen.fillOval(150+x, 100+y, 10, 10);blueSeta-=3;if(blueSeta=360) blueSeta=0;x=(int)(80.0*Math.cos(3.1415926/180.0*blueSeta);y=(int)(80.0*Math.sin(3.1415926/180.0*blueSeta);bluePen.setColor(Color.blue);/用底色画图,擦除原先所画原点bluePen.fillOval(150+x, 100+y, 10, 10);tryredBall.sleep(20);catch(InterruptedException e)例8.3程序运行界面8.3 线程互斥和同步通常情况下,程序中的多个线程是相互协调和相互联系的,多线程之间有互斥和同步。8.3.1 线程互斥先看线程之间需要互斥的情况。设有若干线程共享某个变量,且都对变量有修改。如果它们之间不考虑相互协调工作,就会产生混乱。比如,线程A和B共有变量x,都对x执行增1操作。由于A和B没有协调,两线程对x的读取、修改和写入操作会相互交叉,可能两个线程读取同样的x值,一个线程将修改后的x新值写入到x后,另一个线程也把自己对x的修改后的新值写入到x。这样,x只记录一个线程的修改作用。【例8.4】应用程序说明多线程共享变量,因设有互相协调生产不正确结果。主线程创建了20个线程,它们都取变量的值,经累加后,将新值存回变量。待这些线程执行结束,产生不正确的结果。public class Example8_4 public static void main(String args)MyResourceClass mrc=new MyResourceClass();Thread aThreadArray=new Thread20;System.out.println(t刚开始的值是:+mrc.getInfo();System.out.println(t预期的正确结果是:+20*1000*50);System.out.println(t多个线程正在工作,请稍等!);for(int i=0;i20;i+)aThreadArrayi=new Thread(new MyMultiThreadClass(mrc);aThreadArrayi.start();WhileLoop:while(true)for(int i=0;i20;i+)if(aThreadArrayi.isAlive() continue WhileLoop;break;System.out.println(t最后的结果是:+mrc.getInfo();class MyMultiThreadClass implements RunnableMyResourceClass UseInteger;MyMultiThreadClass(MyResourceClass mrc)UseInteger=mrc;public void run()int i,LocalInteger;for(i=0;i1000;i+)LocalInteger=UseInteger.getInfo();LocalInteger+=50;tryThread.sleep(10);catch(InterruptedException e)UseInteger.putInfo(LocalInteger);class MyResourceClassint IntegerRecource;MyResourceClass()IntegerRecource=0;public int getInfo()return IntegerRecource;public void putInfo(int info)IntegerRecource=info;程序执行的输出结果是:刚开始的值是:0预期的正确结果是:1000000多个线程正在工作,请稍等!最后的结果是:50000造成最后结果不正确的原因是,可能有多个线程取得的是同一个值,各自累加并存入,累加的慢的,后存入的线程把别的执行块的线程的修改覆盖掉了。所以,最终结果比预期的要小得多。解决多线程互斥的办法是,某个线程在使用共享变量时,别的线程暂时等待,等待正在使用共享变量的线程使用结束。等到前一个线程使用结束后,才能等待使用共享变量的其他线程中的某一个使用它,而别的线程继续等待。如果保证它们是逐个使用共享变量,再多的线程使用共享变量也不会产生混乱。多线程互斥使用共享资源的程序段,在操作系统中称为临界段。临界段是一种加锁的机制,与多线程共享资源有关。临界段的作用是在任何时刻一个共享资源只能供一个线程使用。当资源未被占用,线程可以进入处理这个资源的临界段,从而得到该资源的使用权;当线程执行完毕,便退出临界段。如果一个线程已进入某个共享资源的临界段,并且还没有使用结束,其他线程必须等待。在Java语言中,使用关键字synchronized定义临界段,能对共享对象的操作上锁。如果修改例8.4程序,另定义一个累计修改的方法,并将这个方法作为临界段,即某一时刻只允许一个线程做累计修改工作,这就能保证得到正确的结果。修改后的run()方法,及MyResourceClass类新增的方法如下:public void run()for(int i=0;i1000;i+)UseInteger.sumPesource(50);tryThread.sleep(10);catch(InterruptedException e)class MyResourceClassint IntegerRecource;MyResourceClass()IntegerRecource=0;public int getInfo()return IntegerRecource;public void putInfo(int info)IntegerRecource=info;synchronized void sumPesource(int q)int LocalInteger;LocalInteger=getInfo();LocalInteger+=q;putInfo(LocalInteger);改进后,例8.4的程序如下:public class Example8_4 public static void main(String args)MyResourceClass mrc=new MyResourceClass();Thread aThreadArray=new Thread20;System.out.println(t刚开始的值是:+mrc.getInfo();System.out.println(t预期的正确结果是:+20*1000*50);System.out.println(t多个线程正在工作,请稍等!);for(int i=0;i20;i+)aThreadArrayi=new Thread(new MyMultiThreadClass(mrc);aThreadArrayi.start();WhileLoop:while(true)for(int i=0;i20;i+)if(aThreadArrayi.isAlive() continue WhileLoop;break;System.out.println(t最后的结果是:+mrc.getInfo();class MyMultiThreadClass implements RunnableMyResourceClass UseInteger;MyMultiThreadClass(MyResourceClass mrc)UseInteger=mrc;public void run()for(int i=0;i1000;i+)UseInteger.sumPesource(50);tryThread.sleep(10);catch(InterruptedException e)class MyResourceClassint IntegerRecource;MyResourceClass()IntegerRecource=0;public int getInfo()return IntegerRecource;public void putInfo(int info)IntegerRecource=info;synchronized void sumPesource(int q)int LocalInteger;LocalInteger=getInfo();LocalInteger+=q;putInfo(LocalInteger);程序执行的输出结果是:刚开始的值是:0预期的正确结果是:1000000多个线程正在工作,请稍等!最后的结果是:10000008.3.2 线程同步多线程之间除有互斥情况外,还需要同步。当线程A使用到某个对象,而此对象又需要线程B修改后才能符合本线程的需要,这时线程A就要等待线程B完成修改工作。这种线程相互等待称为线程的同步。为实现同步,Java语言提供wait()、notify()和nodifyAll()三个方法供线程在临界段中使用。在临界段中使用wait()方法,使执行该方法的线程等待,并允许其他线程使用这个临界段。wait()方法常用以下两种格式:wait(),让线程一直等待,直到被nodify()或nodifyAll方法唤醒。wait(long timeout),让线程等待到被唤醒,或经过指定时间后结束等待。当线程使用完临界段后,用nodify()方法通知由于想使用这个临界段而处于等待的线程结束等待。Nodify()方法只是通知第一个处于等待的线程。如果某个线程在使用完临界段方法后,其他早先等待的线程都可结束等待,重新竞争CPU,则可以使用nodifyAll()方法。【例8.5】小应用程序模拟一群顾客购买纪念品。设纪念品5元一个,顾客有人持5元、有人持10圆购买纪念品。持5元的顾客能立即买到纪念品,持10元的顾客,如果销售员有可找的小面额钱,则也能立即购买;如果销售员没有可找的小面额钱,则这位顾客就得等待。程序模拟这群顾客购纪念品的过程。class SalesLadyint memontoes,five,ten;public synchronized String ruleForSale(int num,int money)String s=null;if(memontoes=0)return 对不起,已售完;if(money=5)memontoes-;five+;s=给你一个纪念品,+你的钱正好。;/销售员的回答else if(money=10)while(five1)trySystem.out.println( +num+号顾客用10元钱购票,发生等待!);wait();catch(InterruptedException e)memontoes-;five-=1;ten+;s=给你一个纪念品+你给了十元,找你五元;notify();return s;SalesLady(int m,int f,int t)memontoes=m;five=f;ten=t;public class Example8_5 extends java.applet.Appletstatic SalesLady saleLady=new SalesLady(14,0,0);public void start()int moneies=10,10,5,10,5,10,5,5,10,5,10,5,5,10,5;Thread aThreadArray=new Thread20;System.out.println(现在开始购票:);for(int i=0;imoneies.length;i+)aThreadArrayi=new Thread(new CustomerCalss(i+1,moneiesi);aThreadArrayi.start();WhileLoop:while(true)for(int i=0;imoneies.length;i+)if(aThreadArrayi.isAlive()continue WhileLoop;break;System.out.
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 行政人事总监工作总结
- 糖尿病高渗状态护理要点
- 《经络腧穴学》课件
- 老年护理学的休息
- 活动类工作汇报
- 经管部门年度工作总结
- 《瓶中吹气球》课件
- 广东省清远市英德市2023-2024学年高三上学期第一次月考地理试题含参考答案
- 2025合同采购申请评审流程
- 骨包虫病护理查房
- 07第七讲 发展全过程人民民主
- 新汉语水平考试HSK级写作解题攻略专题培训课件
- 学习提高阅读速度的方法 课件
- 自主移动机器人教学课件第4章 导航规划 2 避障规划和轨迹规划
- GB 31628-2014食品安全国家标准食品添加剂高岭土
- GA/T 1312-2016法庭科学添改文件检验技术规程
- 卫生政策学之政策问题根源分析
- 步进电机及其工作原理-电机的工作原理及特性课件
- 基于CAN通讯的储能变流器并机方案及应用分析报告-培训课件
- 腹直肌分离康复(产后康复课件PPT)
- 聚合物成型的理论基础课件
评论
0/150
提交评论