第7章多线程与异常处理主要内容:•异常处理•多线程的基本概念•线程的使用方法•线程同步7.1异常处理异常(Exception)指程序运行过程中出现的非正常现象,例如用户输入错误、需要处理的文件不存在、在网络上传输数据但网络没有连接等。7.1.1Java的异常处理机制在Java中,把异常分为错误(Error)与异常(Exception)两大类。Error定义了程序中不能恢复的严重错误条件。如内存溢出、类文件格式错误等。这一类错误由Java运行系统处理,不需要我们去处理。Exception定义了程序中遇见的轻微的错误条件。7.1.1Java的异常处理机制系统定义的运行异常系统定义的运行异常说明ClassNotFoundException找不到要装载的类,由Class.forName抛出ArrayIndexOutOfBoundsException数组下标出界FileNotFoundException找不到指定的文件或目录IOException输入输出错误NullPointerException非法使用空引用ArithmeticException算术错误,如除数为0InterruptedException一个线程被另一个线程中断UnknownHostException无法确定主机的IP地址SecurityException安全性错误MalfomedURLExceptionURL格式错误7.1.2异常的抛出【例7-1】创建一个有错误的程序,测试异常抛出的情况。(默认情况下,系统自动抛出异常)1./*测试除数为0时抛出的异常*/2.classExample7_13.{4.publicstaticvoidmain(String[]args)5.{6.inta=5,d=0;7.System.out.println(a/d);8.}9.}7.1.3异常处理异常处理的方法有二种:一种方法是使用try…catch…finally结构对异常进行捕获和处理;另一种方法是通过throws和throw抛出异常。1.try…catch…finally结构格式为:try{可能出现异常的程序代码}catch(异常类1变量1){异常类1对应的异常处理代码}catch(异常类2变量2){异常类2对应的异常处理代码}...[finally]{无论异常是否发生都要执行的代码}【例7-2】应用异常处理修改例7-1。classExample7_2{publicstaticvoidmain(String[]args){inta=5,b=0;try{System.out.println(a/b);}catch(Exceptione){System.out.println(“除数为0);}}}【例7-3】数组下标越界引发异常classExample7_3{publicstaticvoidmain(String[]args){inta[]={1,2,3,4,5};intsum=0;try{for(inti=0;i=5;i++){sum+=a[i];}System.out.println(sum=+sum);}catch(ArrayIndexOutOfBoundsExceptione){System.out.println(发生异常原因+e);}finally{System.out.println(程序运行结束);}}}用throw语句抛出异常对象的语法格式为:修饰符返回类型方法名()throws异常类名{….thrownew异常类名();….}2、声明抛出异常Exception类从基类Throwable继承的方法:StringgetMessage():取得细节消息StringgetLocalizedMessage():取得细节消息(特定语系)StringtoString():返回Throwable的简短描述(详细的消息,如果有的话)voidprintStackTrace():打印出Throwable和Throwable的调用堆栈路径ThrowablefillStackTrace():把Stack状态记录于Throwable,用于重掷publicclassExceptionMethods{publicstaticvoidmain(String[]args){try{thrownewException(Heres’smyException);}catch(Exceptione){System.err.println(CaughtException);System.err.println(e.getMessage():+e.getMessage());System.err.println(e.getLocalizedMessage():+e.getLocalizedMessage());System.err.println(e.toString():+e);System.err.println(e.printStackTrace():);e.printStackTrace(System.err);}}}System.err:输出错误信息;优于System.out,因为把结果送到System.err,它不会随System.out一起被重定向。输出结果:CaughtExceptione.getMessage():Here'smyExceptione.getLocalizedMessage():Here'smyExceptione.toString():java.lang.Exception:Here'smyExceptione.printStackTrace():java.lang.Exception:Here'smyExceptionatExceptionMethods.main(ExceptionMethods.java:6)每个方法都比前一个提供了更多的信息----实际上它们每一个都是前一个的超集。7.2多线程的基本概念7.2.1线程与进程所谓“进程”(process),是一个独立运行着的程序,它有自己的地址空间。线程是进程内部的单一控制执行流。因此一个进程内可以具有多个并发执行的线程。7.2.2什么是多任务?“多任务”(multitasking)是计算机操作系统同时运行几个程序(进程)或任务的能力。严格地说,一个单CPU计算机在任何给定的时刻只能执行一个任务,然而操作系统可以在很短的时间内在各个程序之间进行切换,这样看起来就好象计算机在同时执行多个程序。一个程序具备有同时执行不同任务的能力,称为“多线程”。7.2.3进程与多线程的区别在Java编程中,每实例化一个线程对象,就创建一个虚拟的CPU,由虚拟CPU处理本线程数据。每个Java程序都有一个主线程,即由main()方法所对应的线程。对于applet,浏览器即是主线程。除主线程外,线程无法自行启动,必须通过其他程序来启动它。7.2.4Java的多线程机制7.2.5线程的生命周期线程要经历创建、就绪、运行、阻塞和死亡等5个状态,称为生命周期。1、创建状态当我们通过new命令创建了一个线程对象,则该线程对象就处于创建状态。如下面语句所示:Threadthread1=newThread();创建状态是线程已被创建但未开始执行的一个特殊状态。此时线程对象拥有自己的内存空间,但没有分配CPU资源,需通过start()方法调度进入就绪状态等待CPU资源。2、就绪状态处于创建状态的线程对象通过start()方法进入就绪状态,如下面语句所示:Threadthread1=newThread();Thread1.start();start()方法同时调用了线程体,也就是run()方法,表示线程对象正等待CPU资源,随时可被调用执行。(即只要调度程序把时间片分配给线程,线程就可以运行了)3、运行状态若线程处于正在运行的状态,表示线程已经拥有了对处理器的控制权,其代码目前正在运行,除非运行过程中控制权被另一优先级更高的线程抢占,否则这一线程将一直持续到运行完毕。4、阻塞状态一个线程进入阻塞状态,可能有如下原因:1.你通过调用sleep(milliseconds)使线程进入休眠状态,在这种情况下,线程在指定的时间内不会运行。2.你通过调用wait()使线程挂起。直到线程得到了notify()或notifyAll()消息,线程才会进入就绪状态。3.线程在等待某个输入/输出完成。4.线程试图在某个对象上调用其同步控制方法,但是对象锁不可用。5、死亡状态死亡状态(或终止状态),表示线程已退出运行状态,并且不再进入就绪队列。一个线程的生命周期一般经过如下步骤:(1)一个线程通过new()操作实例化后,进入创建状态。(2)通过调用start()方法进入就绪状态,一个处在就绪状态的线程将被调度执行,执行该线程相应的run()方法中的代码。(3)通过调用线程的(或从Object类继承过来的)sleep()或wait()方法,这个线程进入阻塞状态。一个线程也可能自己完成阻塞操作。(4)当run()方法执行完毕,或者有一个例外产生,或者执行System.exit(0)方法,则一个线程就进入死亡状态。7.3线程的创建方式在Java语言中,可采用两种方式产生线程:(1)通过创建Thread类的子类来构造线程。Java定义了一个直接从根类Object中派生的Thread类。所有从这个类派生的子类或间接子类,均为线程。(2)通过实现一个Runnable接口的类来构造线程。7.3.1创建Thread子类构造线程线程的创建与启动:(1)创建一个Thread类的子类;(2)在子类中重新定义自己的run()方法,这个中包含了线程要实现的操作;(3)用关键字new创建一个线程对象;(4)调用start()方法启动线程。【例7-4】创建二个Thread类的子类,然后在另一个类中建立这2个Thread类的对象来测试它,看具体会发生什么现象。这个例子说明了这样几个事实:(1)创建独立执行线程比较容易,Java负责处理了大部分细节。(2)各线程并发运行,共同争抢CPU资源,线程抢夺到CPU资源后,就开始执行,无法准确知道某线程能在什么时候开始执行。(3)线程间的执行是相互独立的。(4)线程独立于启动它的线程(或程序)。7.3.2实现Runnable接口构造线程线程构造的步骤如下:1、实现Runnable接口2、定义run()方法3、构造线程:Thread(Runnable对象名);4、启动线程:线程对象.start();【例7-5】创建一个实现Runnable接口的线程类,然后在另一个类中建立2个线程对象来测试它,看具体会发生什么现象。【例7-6】我们用Thread子类程序来模拟航班售票系统,实现四个售票窗口发售某班次航班的100张机票,一个售票窗口用一个线程来表示。【例7-7】用Runnable接口程序来模拟航班售票系统,实现四个售票窗口发售某班次航班的100张机票,一个售票窗口用一个线程来表示。【例7-8】设计一个多线程的应用程序,模拟一个台子上有多个弹子在上面滚动。“弹子”在碰到“台子”的边缘时会被弹回来。7.4线程同步由于多线程要共享内存资源,因此有可能一个线程正在使用某个资源,而另一个线程却在更新它,这样,会造成数据的不正确。因此对于多个线程共享的资源,必须采取措施,使得每次只有一个线程能使用它,这就是多线程中的同步(synchronization)问题。访问受限资源的第一个线程给资源加锁,接着其它线程就只能等到锁被解除以后才能访问该资源。7.4.1使用多线程造成的数据混乱【例7-9】设计一个模拟用户从银行取款的应用程序。设某银行帐户存款额的初值是2000元,用线程模拟两个用户从银行取款的情况。通过对该程序的分析,发现出现错误结果的根本原因是两个并发线程共享同一内存变量所引起的。后一线程对变量的更改结果覆盖了前一线程对变量的更改结果,造成数据混乱。7.4.2同步线程