




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第8章多线程与异常机制本章学习目标理解什么是多线程掌握线程的创建方法、生命期及状态掌握线程的调度方法和优先级设置方法了解线程组的概念及其实现方法熟悉异常机制8.1多线程概述进程和线程单线程和多线程Java语言支持多线程说明进程和线程进程:一个动态执行的程序。当你运行一个程序的时候,就创建了一个用来容纳组成代码和数据空间的进程。线程:进程中单一顺序的执行流,线程可以共享内存单元和系统资源,但不能够单独执行,必须存在于某个进程当中。单线程和多线程单线程:一个进程中只包含一个线程,也就是说一个程序只有一条执行路线。多线程:在单个进程中可以同时运行多个不同的线程执行不同的任务。Java支持多线程Java线程由以下三部分组成:虚拟的CPUCPU所执行的代码CPU所处理的数据
虚拟的CPU被封装在java.lang.Thread类中,有多少个线程就有多少个虚拟的CPU在同时运行,提供对多线程的支持。说明执行多线程的时候,Java虚拟处理机在多个线程之间轮流切换,不过每个时刻只能有一个线程在执行。main方法是Java的入口程序,一旦进入就启动了一个main线程。即使main方法执行完最后一句,Java程序也会一直等到所有线程都运行结束后才停止。8.2多线程的创建线程体Thread子类创建线程使用Runnable接口创建线程比较线程体线程中真正执行的语句块称为线程体。方法run()就是一个线程体,在一个线程被建立并初始化以后,系统就自动调用run()方法。8.2.1Thread子类创建线程继承Thread类并重写其中的方法run()来实现,把线程实现的代码写到run()方法中,线程从run()方法开始执行,直到执行完最后一行代码或线程消亡。请看例子8.2.2使用Runnable接口利用Runnable接口可以让其他类的子类实现多线程的创建,这是利用继承Thread类的方法无法办到的。采用该方式来创建线程,还必须引用Thread类的构造方法,把采用Runnable接口类的对象作为参数封装到线程对象当中。请看例子比较使用子类直接继承Thread类的方法创建线程,可以在子类中增加新的成员变量和新的成员函数,使得线程具有新的属性和功能,还可以直接操作线程,但由于java中不支持多继承,因此Thread子类不能扩展其他的类;利用Runnable接口,线程的创建可以从其它类继承,使得代码和数据分开,不过需要使用Thread对象来操纵线程。8.3线程的生命期及其状态线程的状态线程的状态转换图与线程状态有关的Thread类方法8.3.1线程的状态线程的生命期是指从线程被创建开始到死亡的过程。通常包括以下5种状态:新建状态就绪状态运行状态阻塞状态死亡状态
新建状态当用Thread类或其子类创建了线程对象时,该线程对象就处于新建状态,系统为该新线程分配了内存空间和其他资源。就绪状态如果系统资源未满足线程的调度,线程就开始排队,等待CPU的时间片,此时,线程处于就绪状态。有三种情况使得线程进入就绪状态:
新建状态的线程被启动,但不具备运行的条件;处于正在运行的线程时间片结束或调用yield()方法;被阻塞的线程引起阻塞的因素消除了,进入排队队列等待CPU的调度。运行状态当线程被调度获得了CPU控制权的时候,就进入了运行状态。线程在运行状态时,会调用本对象的run()方法。一般在子类中重写父类的run()方法来实现多线程。阻塞状态当运行的线程被人为挂起或由于某些操作使得资源不满足的时候,暂时终止自己的运行,让出CPU,进入阻塞状态。有下面4种原因使得线程进入阻塞状态:在线程运行过程中,调用了wait()方法,使得线程等待。等待中的线程并不会排队等待CPU的调度,必须调用notify()通知方法,才能使它重新进入排队队列等待CPU的时间片,也就是进入就绪状态。在线程运行过程中,调用了sleep(inttime)方法,使得线程休眠。休眠中的线程只有经过休眠时间time之后,才会重新进入排队队列等待CPU的调度,也就是进入了就绪状态。在线程运行过程中,调用了suspend()方法,使得线程挂起。挂起的线程需要调用resume()恢复方法,才能进入就绪状态。在线程运行过程中,由于输入输出流而引起阻塞。被阻塞的线程并不会排队等待CPU的调度,只有引起阻塞的原因消除后,才能使它重新进入排队队列等待CPU的时间片,也就是进入就绪状态。死亡状态线程的run()方法执行完所有的任务正常地结束;线程被stop()方法强制地终止。线程消亡有两种情况:线程的状态转换图8.3.2与线程状态有关的Thread类方法线程状态的判断线程的新建和启动线程的阻塞和唤醒线程的停止线程状态的判断isAlive()方法判断线程是否在运行,如果是,返回true,否则返回false。不管是线程未开启还是结束,isAlive()方法都会返回false。线程的新建和启动通过newThread()方法可以创建出一个线程对象,不过此时Java虚拟机并不知道它,因此,我们需要通过start()方法来启动它。请看例子线程的阻塞和唤醒wait()方法sleep()方法join()方法yield()方法suspend()方法wait()方法publicfinalvoidwait()publicfinalvoidwait(longtime)publicfinalvoidwait(longtime,intargs)调用wait()方法的线程必须通过调用notify()方法来唤醒它。方法定义如下:publicfinalvoidnotify()publicfinalvoidnotifyAll()其中,notify()方法是随机唤醒一个等待的线程
notifyAll()方法是唤醒所有等待的线程。sleep()方法publicstaticvoidsleep(longtime)publicstaticvoidsleep(longtime,intargs)比较Thread的sleep()方法使线程进入睡眠状态,但它并不会释放线程持有的资源,不能被其他资源唤醒,不过睡眠一段时间会自动醒过来,而wait()方法让线程进入等待状态的同时也释放了持有的资源,能被其他资源唤醒。join()方法join()方法是指线程的联合,即在一个线程运行过程中,若其他线程调用了join()方法与当前运行的线程联合,运行的线程会立刻阻塞,直到与它联合的线程运行完毕后才重新进入就绪状态,等待CPU的调度。publicfinalvoidjoin()publicfinalvoidjoin(longtime)publicfinalvoidjoin(longtime,intargs)请看例子yield()方法yield()方法是释放当前CPU的控制权。当线程调用yield()方法的时候,若系统中存在相同优先级的线程,线程将立刻停下调用其它优先级相同的线程,若不存在相同优先级的线程,那么yield()方法将不产生任何效果,当前调用的线程将继续运行。suspend()方法在Java2之前,可以利用suspend()和resume()方法对线程挂起和恢复,但这两个方法可能会导致死锁,因此现在不提倡使用。Java语言建议采用wait()和notify()来代替suspend()和resume()方法。线程的停止使用stop()方法停止一个线程,不过stop()方法是不安全的,停止一个线程可能会使线程发生死锁,所以现在不推荐使用了。Java建议使用其他的方法来代替stop()方法,比如可以把当前线程对象设置为空,或者为线程类设置一个布尔标志,定期地检测该标志是否为真,如要停止一个线程,就把该布尔标志设置为true。请看例子8.4线程的同步示例Synchronized方法方法同步对象同步饿死和死锁示例假设有两个线程Thread1和Thread2同时要访问变量num,线程Thread1对其进行num=num+1的操作,线程Thread2是把num加一后的值附给一个变量data,而线程Thread1的加操作需要三步来执行:把num装入寄存器;对该寄存器加1;把寄存器内容写回num假设在第一步和第二步完成后该线程被切换,如果此时线程Thread2具有更高优先级线程,线程Thread2占用了CPU,紧接着就把num值赋给data,虽然num的值已加1,但是还在寄存器中,于是出现了数据不一致性。为解决共享数据的操作问题,Java语言中引入线程同步的概念。Synchronized方法Java语言中使用关键字synchronized来实现线程的同步。当一个方法或对象使用Synchronized关键字声明的时候,系统就为其设置一特殊的内部标记,称为锁,当一个线程调用该方法或对象的时候,系统都会检查锁是否已经给其他线程了,如果没有,系统就把该锁给它,如果该锁已经被其他线程占用,那么该线程就要等到锁被释放了,才能访问该方法。有时我们需要暂时释放锁,使得其他线程可以调用同步方法,这就可以利用wait()方法来实现。wait()方法可以使持有锁的线程暂时释放锁,直到有其他线程通过notify方法使它重新获得该锁。8.4.1方法同步一个类中任何方法都可以设计成为synchronized方法。我们来看一个例子:
有两个线程:Company和Staff,职员Staff有一个账户,公司每个月把工资存到该职员的账户上,该职员可以从账户上领取工资,职员每次要等Company线程把钱存到账户后,才能从账户上领取工资,这就涉及线程的同步机制。请看例子8.4.2对象同步synchronized除了像上面讲的放在方法前面表示整个方法为同步方法外,还可以放在对象前面限制一段代码的执行,实现对象同步。请看例子另一种方法是使用Object对象来上锁:请看例子8.4.3饿死和死锁饿死:如果一个线程执行很长时间,一直占着CPU资源,而使 得其它线程不能运行,就可能导致“饿死”。死锁:如果两个或多个线程都在互相等待对方持有的锁,那么 这些线程都进入阻塞状态,永远等待下去,无法执行,程序就出现了死锁。请看例子8.5线程的优先级和调度线程的优先级
线程的调度8.5.1线程的优先级Java中,给每个线程赋一个从1到10整数值来表示多线程优先级,优先级决定了线程获得CPU调度执行的优先程度。Thread.MIN_PRIORITY(通常为1)的优先级最小;Thread.MAX_PRIORITY(通常为10)优先级最高,NORM_PRIORITY表示缺省优先级,默认值为5。优先级的操作获得线程的优先级
intgetPriority();改变线程的优先级
voidsetPriority(intnewPriority)请看例子8.5.2线程的调度Java调度器调度遵循以下原则:优先级高的线程比优先级低的线程线程先调度。优先级相等的线程按照排队队列的顺序进行调度。先到队列的线程先被调度。在时间片方式下,优先级高的线程要等优先级低的线程时间片运行完毕才能被调度。在抢占式调度方式下,优先级高的线程可以立刻获得CPU的控制权。由于优先级低的线程只有等优先级高的线程运行完毕或优先级高的线程进入阻塞状态才有机会运行,为了让优先级低的线程也有机会运行,通常会不时让优先级高的线程进入睡眠或等待状态,让出CPU的控制权。8.6守护线程
setDaemon(booleanon)方法是把调用该方法的线程设置为守护线程。线程默认为非守护线程,也就是用户线程。当我们把一个线程设置为守护线程时,守护线程在所有非守护线程运行完毕后它的run()方法还没执行结束,守护线程也会立刻结束。把一个线程设置为守护线程方式如下:
thread.setDaemon(true)请看例子8.7线程组入门
线程组的构造ThreadGroup类的一些方法入门线程组是把多个线程集成到一个对象里并可以同时管理这些线程。每个线程组都拥有一个名字以及与它相关的一些属性。每个线程都属于一个线程组。在线程创建时,可以将线程放在某个指定的线程组中,也可以将它放在一个默认的线程组。若创建线程而不明确指定属于哪个组,它们就会自动归属于系统默认的线程组。一旦线程加入了某个线程组,它将一直是这个线程组的成员,而不能改变到其他的组。线程组的构造以下三种Thread类的构造方法实现线程创建的同时指定其属于哪个线程组。publicThread(ThreadGroupgroup,Runnabletarget)publicThread(ThreadGroupgroup,Stringname)publicThread(ThreadGroupgroup,Runnabletarget,Stringname)ThreadGroup类的一些方法activeCount() //返回线程组中当前所有激活的线程的数目。activeGroupCount()//返回当前激活的线程作为父线的线程组的数目。getName() //返回线程组的名字。getParent()//返回该线程的父线程组的名称。setMaxPriority(intpriority)//设置线程组的最高优先级。getMaxPriority()//获得线程组包含的线程中的最高优先级。getThreadGroup()//返回线程组。isDestroyed()//判断线程组是否已经被销毁。destroy() //销毁线程组及其它包含的所有线程。interrupt()//向线程组及其子组中的线程发送一个中断信息。parentOf(ThreadGroupgroup)//判断线程组是否是线程组group或其子线程组。setDaemon(booleamdaemon)//将该线程组设置为守护状态。isDaemon()//判断是否是守护线程组。ThreadGroup类的一些方法list()//显示当前线程组的信息。toString()//返回一个表示本线程组的字符串。enumerate(Thread[]list)//将当前线程组中所有的线程复制到list数组中。enumerate(Thread[]list,booleanargs) //将当前线程组中所有的线程复制到list数组中,若args为true,则把所有子线程组中的线程复制到list数组中。enumerate(ThreadGroup[]group) //将当前线程组中所有的子线程组复制到group数组中。enumerate(ThreadGroup[]group,booleanargs) //将当前线程组中所有的子线程组复制到group数组中,若args为true,则把所有子线程组中的子线程组复制到group数组中。请看例子8.8异常机制
在Java语言中,将程序编译或执行中发生的不正常情况称为“异常”。异常可分为编译时异常和运行时异常。Java定义了异常类Exception,然后由它派生出RuntimeException、IOException(FileNotFoundException为其子类异常类)、ReflectiveOperationException(ClassNotFoundException为其子类异常类)和SQLException等,RuntimeException为运行时异常,其他如IOException和SQLException等为checked异常,即编译时要检查的异常。
8.8.1异常示例【例8-11】编译时异常。importjava.io.*;publicclassTestIOException{publicstaticvoidmain(String[]args)//throwsIOException{ charsex; System.out.println("请输入性别代号:");sex=(char)System.in.read();if(sex!='u'){if(sex=='m')System.out.println("男性");if(sex=='f')System.out.println("女性");}elseSystem.out.println("未知");}}上述程序编译报错如下:
c:\工作目录>javacTestIOException.java
TestIOException.java:6:错误:未报告的异常错误IOException;必须对其进行捕获或声明以便抛出sex=(char)System.in.read();^上面6表示是代码第6行System.in.read()方法引发的异常,异常类型为IOException,编译系统提示:必须对其进行捕获或声明以便抛出。上述代码中将注释//去掉,抛出该异常,编译便成功。又比如第10章将介绍的Java输入输出中,【例10-6】有如下一行代码:
FileInputStreamfis=newFileInputStream("data.dat");newFileInputStream("data.dat")可能会由于文件data.dat路径不对或名称不对,而引发FileNotFoundException,若未对其进行该异常捕获或抛出声明,则编译将失败。此外,初学者在编译程序时,也常会遇到ClassNotFoundException的编译时异常,这种情况,往往是由于环境变量classpath的路径设置漏了或设置不对以及包名(包目录)可能有误而引发,读者注意Check下,确保编译系统编译时能找到相应的类,问题就解决了。下面看几个运行时异常的例子:publicvoidRunTimeE1(){shortx[]=newshort[6];System.out.println(x[7]);//引发ArrayIndexOutOfBoundsException数组下标越界异常}publicvoidRunTimeE2(){Stringstr="ok";
inti=Integer.parseInt(str);//引发NumberFormatException数字格式异常}publicvoidRunTimeE3(){char[]s=null;
System.out.println(s[1]);//引发NullPointerException空指针异常,即空引用}publicvoidRunTimeE4(){inti=10;
intj=0;
intk=i/j;//引发ArithmeticException算数异常,即除0异常}8.8.2异常抛出和处理【例8-11】通过抛出异常throwsIOException的声明解决了其编译时异常问题。异常抛出:throws和throw异常处理:try-catch-finally异常抛出:throws和throw
1)throws出现在方法的声明中,表示该方法中的代码可能会引发异常,故将其抛出,交给上层调用它的方法处的程序进行处理,若该方法调用处也没有处理,则须继续向上抛出,直至得到处理,若一直到main方法中也得不到处理,则须在main方法声明处将其继续抛出,抛给JVM(JavaVirtualMachine,Java虚拟机,即Java[运行时]系统)进行默认处理,也就是抛给Java系统内置的异常机制进行处理,此时用户程序将被中断退出,JVM系统会给出相应异常信息提示,指导用户对程序进行纠错,将异常控制在用户程序内进行处理,避免程序异常退出。比如【例8-11】中,main方法就被Java编译系统强制要求抛出IOException,因为main方法中调用了库类的System.in.read()方法,而该方法,Java系统规定:必须对其进行捕获处理或声明以便抛出。Java允许throws声明后面跟着多个异常类型,多个异常类型间以逗号间隔开,throws声明位于方法声明的尾部。
为什么【例8-11】没有进行异常捕获处理呢?这是因为它的代码简单,用户任意输入,都不会引发异常,假如它的代码复杂,在System.in.read()方法被调用前,出于某种需要,System.in.close()方法被调用了,即in这个输入流对象被关闭,则此时将引发IOException,这种情况下,为了避免程序异常退出,就必须对其进行捕获处理,而不仅仅是抛给JVM系统去处理了。
2)throws是异常的抛出声明,而throw则是抛出异常,即引发异常,引发异常有两种,一种是程序代码问题被动引发异常,一种是throw语句主动(抛出)引发异常。throw只会出现在方法体中,当方法在执行过程中遇到异常情况时,主动生成异常对象,然后throw出去,让本方法或上(上)层调用它的方法通过try-catch语句进行捕获处理。throw常用于程序出现某种逻辑错误时程序员主动抛出某种特定类型的异常,即程序员用户的自定义异常类。
throws声明表示本方法体内的代码有可能会引发异常;throw则是抛出(引发)了异常,执行throw语句一定引发异常,因为它生成并抛出了某种异常对象。异常处理:try-catch-finallytry{…//可能出现异常的代码}catch(XExceptione){…//处理XException的代码}catch(YExceptione){…//处理YException的代码}catch(ZExceptione){…//处理ZException的代码}…finally{…//一定会执行的代码}1)finally是可选的。2)使用try将可能会出现异常的代码段包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配。3)一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况)。继续执行其后的代码。4)catch中的异常类型如果没有父子类关系,则谁声明在上,谁声明在下无所谓。catch中的异常类型如果满足父子类关系,则要求子类一定声明在父类的上面。否则,报错。5)finally中编写的代码一定会被执行,主要用于将try中生成的资源对象进行释放,如输入输出流、数据库连接等。6)程序继续执行。有了try-catch-finally异常处理的保护,代码的健壮性得以增强,避免了代码异常导致的程序中断退出。【例8-12】前面几个运行时异常的异常处理。程序在JDK17下编译成功,运行如下所示:c:\工作目录>javaProcessRunTimeExceptions数组下标越界异常:Index7outofboundsforlength6数字格式异常:Forinputstring:"ok"空指针异常:Cannotloadfromchararraybecause"<local3>"isnull算数异常即除0异常:/byzero上述几个运行时异常都被处理了,程序不会异常退出!异常处理成功,程序继续执行...上面几个异常,只要有一个没被处理,程序将在引发异常处即中断退出。然而,运行时异常在实际编程过程中,往往会由于程序员的疏忽或者程序规模较大较难掌控而引发,对于程序运行一次就会引发的异常,程序员可以立刻查找原因,对引发异常的代码进行异常处理,而对于其他一些异常,可能要运行多次,才会发现有异常,因此,程序员在交付程序前,务必要多测试几次,关于软件测试,也是一门学问,将来,大家如果参与实际项目开发了,就会体会到。【例8-13】主动抛出异常并进行异常处理。publicclassLoginException{publicstaticvoidlogin(Stringuser,Stringpwd){if(user==null||pwd==null)thrownewNullPointerException("用户名或者密码为空");//主动抛出异常//...}publicstaticvoidmain(String[]args){try{Stringuser=null;Stringpwd=null;login(user,pwd);}catch(Exceptione){System.out.println(e.getMessage());//getMessage获得的信息见运行输出}}}程序编译、运行输出如下:c:\工作目录>javaLoginException用户名或者密码为空8.8.3异常类Java类库自带定义了很多异常类,它们的根类是Throwable,如下图所示:1.库类异常类Java库类中的异常类根类是Throwable,由它派生出了两个子类:Exception类和Error类。Throwable类提供了三个非常有用的方法:(1)StringgetMessage():获取异常的描述信息;(2)StringtoString():获取异常的类型、异常描述信息;(3)voidprintStackTrace():打印异常的跟踪栈信息并输出到控制台,但不能在System.out.println()中使用该方法;其中包含了异常的类型、异常的原因、异常出现的位置;在开发和调试阶段,该方法很有用,方便调试和修改;由于Throwable是根类,它的子类Exception类和Error类及它们再派生出的所有子类,都继承了该方法。【例8-15】Throwable类的三个常用方法。publicclassProcessRunTimeExceptions1{publicstaticvoidmain(String[]args){shortx[]=newshort[6];try{System.out.println(x[7]);}catch(ArrayIndexOutOfBoundsExceptione){System.out.println(e.getMessage());System.out.println(e.toString());System.out.println("异常栈追踪:");e.printStackTrace();}}}编译,运行如下:c:\工作目录>javaProcessRunTimeExceptions1Index7outofboundsforlength6//e.getMessage();java.lang.ArrayIndexOutOfBoundsException:Index7outofboundsforlength6//e.toString();异常栈追踪://下面是e.printStackTrace();java.lang.ArrayIndexOutOfBoundsException:Index7outofboundsforlength6atProcessRunTimeExceptions1.main(ProcessRunTimeExceptions1.java:5)//5是代码出错行Error类及其子类属于系统错误,不能通过try-catch异常处理加以解决,它们是Java虚拟机也无法解决的,如JVM系统内部错误、资源耗尽等严重错误,Error子类VirtualMachineError又派生了StackOverflowError和OutOfMemoryError等子类错误。【例8-16】StackOverflowError示例。publicclassStackOverflowErrorExample{publicstaticvoidmain(String[]args){
//栈溢出错误:java.lang.StackOverflowErrormain(args);//无限递归导致}}编译成功,但运行如下:c:\工作目录>javaStackOverflowErrorExampleExceptioninthread"main"java.lang.StackOverflowErroratStackOverflowErrorExample.main(StackOverflowErrorExample.java:4)//4是代码出错行atStackOverflowErrorExample.main(StackOverflowErrorExample.java:4)atStackOverflowErrorExample.main(StackOverflowErrorExample.java:4)…………c:\工作目录
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 计算机二级Python代码优化试题及答案
- Msoffice考试的策略调整试题及答案
- 计算机二级MySQL参考资料与试题及答案
- 2025录用信与劳动合同内容不一致劳动合同成为关键依据
- 应试宝典-计算机二级Delphi试题及答案
- 绿色战略与风险管理的互动试题及答案
- 2025年Delphi学习计划中试题及答案
- 数组与列表相关考核试题及答案
- 2025新商铺租赁合同范本
- MySQL数据库安全防护试题及答案
- 警民联调工作实施方案
- 实名制考勤管理制度
- 破旧木屋修缮方案范本
- 食堂大厨考试试题及答案
- 中国现代文学思潮知到课后答案智慧树章节测试答案2025年春杭州师范大学
- 科协座谈会交流发言稿
- 3.17 明朝的灭亡和清朝的建立 课件 2024-2025学年统编版七年级历史下册
- 2025年心理b证笔试试题及答案
- 新版事故调查培训
- 肺癌的科普知识
- JJF(皖) 218-2025 重点排放单位碳排放计量审查规范
评论
0/150
提交评论