1、单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,*,第,13,章,Java,多线程机制,13.1,进程与线程,13.1.1,操作系统与进程,程序是一段静态的代码,它是应用软件执行的蓝本。进程是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程。现代操作系统和以往操作系统的一个很大的不同就是可以同时管理一个计算机系统中的多个进程,即可以让计算机系统中的多个进程轮流使用,CPU,资源,甚至可以让多个进程共享操作系统所管理的资源,13.1.2,进程与线程,线程不是进程,但其行为很像进程,线程是
2、比进程更小的执行单位,一个进程在其执行过程中,可以产生多个线程,形成多条执行线索,每条线索,即每个线程也有它自身的产生、存在和消亡的过程。和进程可以共享操作系统的资源类似,线程间也可以共享进程中的某些内存单元,(,包括代码与数据,),,并利用这些共享单元来实现数据交换、实时通信与必要的同步操作,但与进程不同的是,线程的中断与恢复可以更加节省系统的开销。具有多个线程的进程能更好地表达和解决现实世界的具体问题,多线程是计算机应用开发和程序设计的一项重要的实用技术,13.2 Java,中的线程,13.2.1 Java,的多线程机制,每个,Java,应用程序都有一个缺省的主线程。,Java,应用程序总
3、是从主类的,main,方法开始执行。当,JVM,加载代码,发现,main,方法之后,就会启动一个线程,这个线程称作“主线程”,该线程负责执行,main,方法。那么,在,main,方法的执行中再创建的线程,就称为程序中的其它线程。如果,main,方法中没有创建其他的线程,那么当,main,方法执行完最后一个语句,即,main,方法返回时,,JVM,就会结束我们的,Java,应用程序。如果,main,方法中又创建了其他线程,那么,JVM,就要在主线程和其他线程之间轮流切换,保证每个线程都有机会使用,CPU,资源,,main,方法即使执行完最后的语句,(,主线程结束,),,,JVM,也不会结束,Ja
4、va,应用程序,,JVM,一直要等到,Java,应用程序中的所有线程都结束之后,才结束,Java,应用程序,操作系统让各个进程轮流执行,那么当轮到,Java,应用程序执行时,,Java,虚拟机就保证让,Java,应用程序中的多个线程都有机会使用,CPU,资源,即让多个线程轮流执行。如果机器有多个,CPU,处理器,那么,JVM,就能充分利用这些,CPU,,获得真实的线程并发执行效果,13.2.2,线程的状态与生命周期,新建的线程在它的一个完整的生命周期中通常要经历如下的四种状态,1,新建,当一个,Thread,类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。此时它已经有了相应的内存空
5、间和其他资源,2,运行,线程创建之后就具备了运行的条件,一旦轮到它来享用,CPU,资源时,即,JVM,将,CPU,使用权切换给该线程时,此线程的就可以脱离创建它的主线程独立开始自己的生命周期了,3,中断,有,4,种原因的中断,JVM,将,CPU,资源从当前线程切换给其他线程,使本线程让出,CPU,的使用权处于中断状态。,线程使用,CPU,资源期间,执行了,sleep(int,millsecond,),方法,使当前线程进入休眠状态,.,线程使用,CPU,资源期间,执行了,wait(),方法,使得当前线程进入等待状态。,线程使用,CPU,资源期间,执行某个操作进入阻塞状态,比如执行读,/,写操作引
6、起阻塞。,4,死亡,处于死亡状态的线程不具有继续运行的能力。线程死亡的原因有二,一个是正常运行的线程完成了它的全部工作,即执行完,run(),方法中的全部语句,结束了,run(),方法。另一个原因是线程被提前强制性地终止,即强制,run(),方法结束。所谓死亡状态就是线程释放了实体,即释放分配给线程对象的内存。,13.2.3,线程调度与优先级,Java,虚拟机(,JVM,)中的线程调度器负责管理线程,调度器把线程的优先级分为,10,个级别,分别用,Thread,类中的类常量表示。每个,Java,线程的优先级都在常数,1,和,10,之间,即,Thread.MIN_PRIORITY,和,Threa
7、d.MAX_PRIORITY,之间。如果没有明确地设置线程的优先级别,每个线程的优先级都为常数,5,,即,Thread.NORM_PRIORITY,线程的优先级可以通过,setPriority,(,int,grade,)方法调整,这一方法需要一个,int,类型参数。如果此参数不在,110,的范围内,那么,setPriority,便产生一个,lllegalArgumenException,异常。,getPriority,方法返回线程的优先级,13.3 Thread,的子类创建线程,在,Java,语言中,用,Thread,类或子类创建线程对象。这一节讲述怎样用,Thread,子类创建线程对象。在编
8、写,Thread,类的子类时,需要重写父类的,run(),方法,其目的是规定线程的具体操作,否则线程就什么也不做,因为父类的,run(),方法中没有任何操作语句,13.4,使用,Runnable,接口,使用,Thread,子类创建线程的优点是:可以在子类中增加新的成员变量,使线程具有某种属性,也可以在子类中新增加方法,使线程具有某种功能。但是,,Java,不支持多继承,,Thread,类的子类不能再扩展其他的类,13.4.1,Runnable,接口与目标对象,创建线程的另一个途径就是用,Thread,类直接创建线程对象。使用,Thread,创建线程通常使用的构造方法是:,Thread,(,Ru
9、nnable,target,),该构造方法中的参数是一个,Runnable,类型的接口,我们知道线程间可以共享相同的内存单元,(,包括代码与数据,),,并利用这些共享单元来实现数据交换、实时通信与必要的同步操作。对于,Thread(Runnable,target),构造方法创建的线程,轮到它来享用,CPU,资源时,目标对象就会自动调用接口中的,run(),方法,因此,对于使用同一目标对象的线程,目标对象的成员变量自然就是这些线程共享的数据单元。另外,创建目标对象类在必要时还可以是某个特定类的子类,因此,使用,Runnable,接口比使用,Thread,的子类更具有灵活性,13.4.2,关于,r
10、un,方法启动的次数,在上述例,13-3,中“红蚂蚁”和“黑蚂蚁”是具有相同目标对象的两个线程,当其中一个线程享用,CPU,资源时,目标对象自动调用接口中的,run,方法,当轮到另一个线程享用,CPU,资源时,目标对象会再次调用接口中的,run,方法,也就是说,run(),方法已经启动运行了两次,分别运行在不同的线程中,即运行在不同的时间片内,13.4.3,在线程中启动其它线程,线程通过调用,start(),方法将启动该线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用,CPU,资源时,就可以脱离创建它的主线程独立开始自己的生命周期了。在前面的例子中,都是在主线程中启动的其它线程,实际上也
11、可以再任何一个线程中启动另外一个线程,13.5,线程的常用方法,1,start(),线程调用该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用,CPU,资源时,就可以脱离创建它的线程独立开始自己的生命周期了。需要特别注意的是,线程调用,start(),方法之后,就不必再让线程调用,start(),方法,否则将导致,IllegalThreadStateException,异常,即只有处于新建状态的线程才可以调用,start(),方法,调用之后就进入排队等待,CUP,状态了,如果再让线程调用,start(),方法显然是多余的,2,run(),Thread,类的,run(),方法与,
12、Runnable,接口中的,run(),方法的功能和作用相同,都用来定义线程对象被调度之后所执行的操作,都是系统自动调用而用户程序不得引用的方法。系统的,Thread,类中,,run(),方法没有具体内容,所以用户程序需要创建自己的,Thread,类的子类,并重写,run(),方法来覆盖原来的,run(),方法。当,run,方法执行完毕,线程就变成死亡状态,所谓死亡状态就是线程释放了实体,即释放分配给线程对象的内存。在线程没有结束,run(),方法之前,不赞成让线程再调用,start(),方法,否则将发生,ILLegalThreadStateException,异常,3,sleep(int,m
13、illsecond,),线程的调度执行是按照其优先级的高低顺序进行的,当高级别的线程未死亡时,低级线程没有机会获得,CPU,资源。有时,优先级高的线程需要优先级低的线程做一些工作来配合它,或者优先级高的线程需要完成一些费时的操作,此时优先级高的线程应该让出,CPU,资源,使优先级低的线程有机会执行。为达到这个目的,优先级高的线程可以在它的,run(),方法中调用,sleep,方法来使自己放弃,CPU,资源,休眠一段时间。休眠时间的长短由,sleep,方法的参数决定,,millsecond,是毫秒为单位的休眠时间。如果线程在休眠时被打断,,JVM,就抛出,InterruptedException
14、异常。因此,必须在,trycatch,语句块中调用,sleep,方法,4,isAlive,(),线程处于“新建”状态时,线程调用,isAlive,(),方法返回,false,。当一个线程调用,start(),方法,并占有,CUP,资源后,该线程的,run(),方法就开始运行,在线程的,run(),方法结束之前,即没有进入死亡状态之前,线程调用,isAlive,(),方法返回,true,。当线程进入“死亡”状态后,(,实体内存被释放,),,线程仍可以调用方法,isAlive,(),,这时返回的值是,false,5,currentThread,(),currentThread,(),方法是,Th
15、read,类中的类方法,可以用类名调用,该方法返回当前正在使用,CPU,资源的线程。,6,interrupt(),intertupt,方法经常用来“吵醒”休眠的线程。当一些线程调用,sleep,方法处于休眠状态时,一个占有,CPU,资源的线程可以让休眠的线程调用,interrupt(),方法“吵醒”自己,即导致休眠的线程发生,InterruptedException,异常,从而结束休眠,重新排队等待,CPU,资源,13.6,线程同步,13.6.1,什么是线程同步,所谓线程同步就是若干个线程都需要使用一个,synchronized,修饰的方法,即程序中的若干个线程都需要使用一个方法,而这个方法用
16、synchronized,给予了修饰。多个线程调用,synchronized,方法必须遵守同步机制:当一个线程使用这个方法时,其他线程想使用这个方法时就必须等待,直到线程,A,使用完该方法。在使用多线程解决许多实际问题时,可能要把某些修改数据的方法用关键字:,synchronized,来修饰,13.6.2,通过同步避免切换的影响,当一个进程中有多个线程时,,JVM,让每个线程都有机会获得,CPU,的使用权,以便使用,CPU,资源执行线程中的操作,即,JVM,轮流让各个线程使用,CPU,,这样一来就会出现程序的运行结果可能依赖,JVM,切换线程使用,CPU,的时机,而,JVM,在线程之间切换使
17、用,CPU,的时机是根据当前,CPU,的具体情况而定的。因此,当一个线程使用,CUP,资源的时,即使线程没有完成自己的全部操作,JVM,也可能会中断当前线程的执行,把,CPU,的使用权切换给下一个排队等待的线程,当前线程将等待,CPU,资源的下一次轮回,然后从中断处继续执行,13.7,在同步方法中使用,wait(),、,notify,和,notifyAll,(),方法,当一个线程使用的同步方法中用到某个变量,而此变量又需要其它线程修改后才能符合本线程的需要,那么可以在同步方法中使用,wait(),方法。,wait,方法可以中断方法的执行,使本线程等待,暂时让出,CPU,的使用权,并允许其它线程
18、使用这个同步方法。其它线程如果在使用这个同步方法时不需要等待,那么它使用完这个同步方法的同时,应当用,notifyAll,(),方法通知所有的由于使用这个同步方法而处于等待的线程结束等待。曾中断的线程就会从刚才的中断处继续执行这个同步方法,并遵循“先中断先继续”的原则。如果使用,notify(),方法,那么只是通知处于等待中的线程的某一个结束等待。,wait(),、,notify(),和,notifyAll,(),都是,Object,类中的,final,方法,被所有的类继承、且不允许重写的方法,13.8,线程联合,一个线程,A,在占有,CPU,资源期间,可以让其它线程调用,join,()和本线程联合,,如:,B.join,();,我们称,A,在运行期间联合了,B,。如果线程,A,在占有,CPU,资源期间一旦联合,B,线程,那么,A,线程将立刻中断执行,一直等到它联合的线程,B,执行完毕,,A,线程再重新排队等待,CPU,资源,以便恢复执行。如果,A,准备联合的,B,线程已经结束,那么,B.join,(),不会产生任何效果,






