配色: 字号:
《Java程序设计教程》08 多线程与异常机制
2023-05-25 | 阅:  转:  |  分享 
  
第8章 多线程与异常机制本章学习目标理解什么是多线程掌握线程的创建方法、生命期及状态掌握线程的调度方法和优先级设置方法了解线程组的概念及其实
现方法熟悉异常机制8.1 多线程概述进程和线程单线程和多线程Java语言支持多线程说明进程和线程进程:一个动态执行的程序。当你运
行一个程序的时候,就创建了一个用来容纳组成代码和数据空间的进程。线程:进程中单一顺序的执行流,线程可以共享内存单元和系统资源,但不
能够单独执行,必须存在于某个进程当中。 单线程和多线程单线程:一个进程中只包含一个线程,也就是说一个程序只有一条执行路线。 多线程
:在单个进程中可以同时运行多个不同的线程执行不同的任务。Java支持多线程Java线程由以下三部分组成:虚拟的CPU CPU所执行
的代码 CPU所处理的数据 虚拟的CPU被封装在java.lang.Thread类中,有多少个线程就有多少个虚拟的CPU在同时运行
,提供对多线程的支持。 说明执行多线程的时候,Java虚拟处理机在多个线程之间轮流切换,不过每个时刻只能有一个线程在执行 。mai
n方法是Java的入口程序,一旦进入就启动了一个main线程 。即使main方法执行完最后一句, Java程序也会一直等到所有线程
都运行结束后才停止 。8.2 多线程的创建线程体Thread子类创建线程使用Runnable接口创建线程比较线程体线程中真正执行
的语句块称为线程体。方法run()就是一个线程体,在一个线程被建立并初始化以后,系统就自动调用run( )方法。8.2.1 Th
read子类创建线程继承Thread类并重写其中的方法run( )来实 现,把线程实现的代码写到run( )方法中,线 程
从run( )方法开始执行,直到执行完最后一 行代码或线程消亡。请看例子8.2.2 使用Runnable接口利用Runnab
le接口可以让其他类的子类实现多线程的创建,这是利用继承Thread类的方法无法办到的。采用该方式来创建线程,还必须引用Threa
d类的构造方法,把采用Runnable接口类的对象作为参数封装到线程对象当中。请看例子比较使用子类直接继承Thread类的方法创建
线程,可以在子类中增加新的成员变量和新的成员函数,使得线程具有新的属性和功能,还可以直接操作线程,但由于java中不支持多继承,因
此Thread子类不能扩展其他的类;利用Runnable接口,线程的创建可以从其它类继承,使得代码和数据分开,不过需要使用Thre
ad对象来操纵线程。8.3 线程的生命期及其状态线程的状态线程的状态转换图与线程状态有关的Thread类方法8.3.1 线程的
状态线程的生命期是指从线程被创建开始到死亡的过程。通常包括以下5种状态: 新建状态 就绪状态运行状态 阻塞状态 死亡状态 新建状态
当用Thread类或其子类创建了线程对象时,该线程对象就处于新建状态,系统为该新线程分配了内存空间和其他资源。就绪状态如果系统资源
未满足线程的调度,线程就开始排队,等待CPU的时间片,此时,线程处于就绪状态。有三种情况使得线程进入就绪状态: 新建状态的线程被启
动,但不具备运行的条件; 处于正在运行的线程时间片结束或调用yield()方法; 被阻塞的线程引起阻塞的因素消除了,进入排队队列等
待CPU的调度。运行状态当线程被调度获得了CPU控制权的时候,就进入了运行状态。线程在运行状态时,会调用本对象的run()方法 。
一般在子类中重写父类的run()方法来实现多线程。 阻塞状态当运行的线程被人为挂起或由于某些操作使得资源不满足的时候,暂时终止自己
的运行,让出CPU,进入阻塞状态。 有下面4种原因使得线程进入阻塞状态:在线程运行过程中,调用了wait()方法,使得线程等待。等
待中的线程并不会排队等待CPU的调度,必须调用notify()通知方法,才能使它重新进入排队队列等待CPU的时间片,也就是进入就绪
状态。在线程运行过程中,调用了sleep(int time)方法,使得线程休眠。休眠中的线程只有经过休眠时间time之后,才会重新
进入排队队列等待CPU的调度,也就是进入了就绪状态。在线程运行过程中,调用了suspend()方法,使得线程挂起。挂起的线程需要调
用resume()恢复方法,才能进入就绪状态。在线程运行过程中,由于输入输出流而引起阻塞。被阻塞的线程并不会排队等待CPU的调度,
只有引起阻塞的原因消除后,才能使它重新进入排队队列等待CPU的时间片,也就是进入就绪状态。死亡状态线程的run()方法执行完所有的
任务正常地结束;线程被stop()方法强制地终止。线程消亡有两种情况:线程的状态转换图8.3.2 与线程状态有关的Thread类
方法线程状态的判断线程的新建和启动线程的阻塞和唤醒线程的停止线程状态的判断isAlive()方法判断线程是否在运行,如果是,返回t
rue,否则返回false。不管是线程未开启还是结束,isAlive()方法都会返回false。线程的新建和启动通过new Thr
ead()方法可以创建出一个线程对象,不过此时Java虚拟机并不知道它,因此,我们需要通过start()方法来启动它。请看例子线程
的阻塞和唤醒wait()方法sleep()方法join()方法yield()方法suspend ()方法wait()方法publi
c final void wait() public final void wait(long time) public fina
l void wait(long time,int args) 调用wait()方法的线程必须通过调用notify()方法来唤醒它
。方法定义如下:public final void notify()public final void notifyAll()其中
,notify()方法是随机唤醒一个等待的线程 notifyAll()方法是唤醒所有等待的线程。 sleep()方法publi
c static void sleep(long time) public static void sleep(long time
,int args)比较Thread的sleep()方法使线程进入睡眠状态,但它并不会释放线程持有的资源,不能被其他资源唤醒,不过
睡眠一段时间会自动醒过来,而wait()方法让线程进入等待状态的同时也释放了持有的资源,能被其他资源唤醒。join()方法join
()方法是指线程的联合,即在一个线程运行过程中,若其他线程调用了join()方法与当前运行的线程联合,运行的线程会立刻阻塞,直到与
它联合的线程运行完毕后才重新进入就绪状态,等待CPU的调度。public final void join()public fina
l void join(long time) public final void join(long time,int args)
请看例子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语言中使用关键字synchroniz
ed来实现线程的同步。当一个方法或对象使用Synchronized关键字声明的时候,系统就为其设置一特殊的内部标记,称为锁,当一个
线程调用该方法或对象的时候,系统都会检查锁是否已经给其他线程了,如果没有,系统就把该锁给它,如果该锁已经被其他线程占用,那么该线程
就要等到锁被释放了,才能访问该方法。有时我们需要暂时释放锁,使得其他线程可以调用同步方法,这就可以利用wait()方法来实现。wa
it()方法可以使持有锁的线程暂时释放锁,直到有其他线程通过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.MA
X_PRIORITY(通常为10)优先级最高, NORM_PRIORITY表示缺省优先级,默认值为5。 优先级的操作获得线程的优先
级 int getPriority();改变线程的优先级  void setPriority(int newPriority)请
看例子8.5.2 线程的调度Java调度器调度遵循以下原则: 优先级高的线程比优先级低的线程线程先调度。优先级相等的线程按照排队队
列的顺序进行调度。先到队列的线程先被调度。在时间片方式下,优先级高的线程要等优先级低的线程时间片运行完毕才能被调度。在抢占式调度方
式下,优先级高的线程可以立刻获得CPU的控制权。由于优先级低的线程只有等优先级高的线程运行完毕或优先级高的线程进入阻塞状态才有机会
运行,为了让优先级低的线程也有机会运行,通常会不时让优先级高的线程进入睡眠或等待状态,让出CPU的控制权。8.6 守护线程 se
tDaemon(boolean on)方法是把调用该方法的线程设置为守护线程。线程默认为非守护线程,也就是用户线程。当我们把一个线
程设置为守护线程时,守护线程在所有非守护线程运行完毕后它的run()方法还没执行结束,守护线程也会立刻结束。把一个线程设置为守护线
程方式如下: thread. setDaemon(true)请看例子8.7 线程组入门 线程组的构造ThreadGroup类
的一些方法入门线程组是把多个线程集成到一个对象里并可以同时管理这些线程。每个线程组都拥有一个名字以及与它相关的一些属性。每个线程
都属于一个线程组。在线程创建时,可以将线程放在某个指定的线程组中,也可以将它放在一个默认的线程组。若创建线程而不明确指定属于哪个组
,它们就会自动归属于系统默认的线程组。一旦线程加入了某个线程组,它将一直是这个线程组的成员,而不能改变到其他的组。 线程组的构造以
下三种Thread类的构造方法实现线程创建的同时指定其属于哪个线程组。public Thread (ThreadGroup gro
up,Runnable target)public Thread (ThreadGroup group,String name)p
ublic Thread (ThreadGroup group,Runnable target,String name)Threa
dGroup类 的一些方法activeCount() //返回线程组中当前所有激活的线
程的数目。activeGroupCount() //返回当前激活的线程作为父线的线程组的数目。getNam
e() //返回线程组的名字。getParent()
//返回该线程的父线程组的名称。setMaxPriority(int priority) //设置线程组的最高优
先级。getMaxPriority() //获得线程组包含的线程中的最高优先级。getThr
eadGroup() //返回线程组。isDestroyed()
//判断线程组是否已经被销毁。destroy() //销毁线程组及其它包含的所有线程。
interrupt() //向线程组及其子组中的线程发送一个中断信息。parentOf(Thre
adGroup group) //判断线程组是否是线程组group或其子线程组。setDaemon(booleam daemon
) //将该线程组设置为守护状态。isDaemon() //判断是否是守护线程组。ThreadG
roup类 的一些方法list() //显示当前线程
组的信息。toString() //返回一个表示本线程组的字符串。en
umerate(Thread[ ] list) //将当前线程组中所有的线程复制到list数组中。enumerate(Th
read[ ] list,boolean args) //将当前线程组中所有的线程复制到list数组中,若args为true
,则把所有子线程组中的线程复制到list数组中。enumerate(ThreadGroup[ ] group) //将当前线程组中
所有的子线程组复制到group数组中。enumerate(ThreadGroup[ ] group,boolean args) /
/将当前线程组中所有的子线程组复制到group数组中,若args为true,则把所有子线程组中的子线程组复制到group数组中。请
看例子8.8 异常机制 在Java语言中,将程序编译或执行中发生的不正常情况称为“异常”。异常可分为编译时异常和运行时异常。Ja
va定义了异常类Exception,然后由它派生出RuntimeException、IOException(FileNotFoun
dException为其子类异常类)、ReflectiveOperationException(ClassNotFoundExce
ption为其子类异常类)和SQLException等,RuntimeException为运行时异常,其他如IOException
和SQLException等为checked异常,即编译时要检查的异常。 8.8.1 异常示例【例8-11】编译时异常。impo
rt java.io.;public class TestIOException{ public static void ma
in(String[] args) // throws IOException { char sex;
System.out.println("请输入性别代号:"); sex = (char)System.in.rea
d(); if ( sex != ''u'' ) { if ( sex == ''m'' )
System.out.println("男性"); if ( sex ==
''f'' ) System.out.println("女性"); }
else System.out.println("未知"); }}上述程序编译报错如下: c
:\工作目录>javac TestIOException.java TestIOException.java:6: 错误
: 未报告的异常错误IOException; 必须对其进行捕获或声明以便抛出 sex = (char)System.
in.read(); ^上面6表示是代码第6行System.in.read
()方法引发的异常,异常类型为IOException,编译系统提示:必须对其进行捕获或声明以便抛出。上述代码中将注释//去掉,抛出
该异常,编译便成功。又比如第10章将介绍的Java输入输出中,【例10-6】有如下一行代码: FileInputSt
ream fis = new FileInputStream("data.dat");new FileInputStream("d
ata.dat")可能会由于文件data.dat路径不对或名称不对,而引发FileNotFoundException,若未对其进行
该异常捕获或抛出声明,则编译将失败。此外,初学者在编译程序时,也常会遇到ClassNotFoundException的编译时异常,
这种情况,往往是由于环境变量classpath的路径设置漏了或设置不对以及包名(包目录)可能有误而引发,读者注意Check下,确保
编译系统编译时能找到相应的类,问题就解决了。下面看几个运行时异常的例子:public void RunTimeE1() { sh
ort x[ ] = new short[6]; System.out.println(x[7]); //引发ArrayInd
exOutOfBoundsException数组下标越界异常}public void RunTimeE2() { String
str = "ok"; int i = Integer.parseInt(str); //引发NumberFormatExce
ption数字格式异常}public void RunTimeE3() { char[] s = null; System.o
ut.println(s[1]); //引发NullPointerException空指针异常,即空引用}public void
RunTimeE4() { int i = 10; int j = 0; int k = i / j; //引发Arith
meticException算数异常,即除0异常}8.8.2 异常抛出和处理【例8-11】通过抛出异常throws IOExce
ption的声明解决了其编译时异常问题。异常抛出:throws和throw异常处理:try-catch-finally异常抛出:t
hrows和throw 1)throws出现在方法的声明中,表示该方法中的代码可能会引发异常,故将其抛出,交给上层调
用它的方法处的程序进行处理,若该方法调用处也没有处理,则须继续向上抛出,直至得到处理,若一直到main方法中也得不到处理,则须在m
ain方法声明处将其继续抛出,抛给JVM(Java Virtual Machine,Java虚拟机,即Java[运行时]系统)进行
默认处理,也就是抛给Java系统内置的异常机制进行处理,此时用户程序将被中断退出,JVM系统会给出相应异常信息提示,指导用户对程序
进行纠错,将异常控制在用户程序内进行处理,避免程序异常退出。比如【例8-11】中,main方法就被Java编译系统强制要求抛出IO
Exception,因为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(XException e){… //处理XExce
ption的代码}catch(YException e){… //处理YException的代码}catch(ZExceptio
n e){… //处理ZException的代码}…finally{… //一定会执行的代码}1)finally是可选的。2)
使用try将可能会出现异常的代码段包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catc
h中进行匹配。3)一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的tr
y-catch结构(在没有写finally的情况)。继续执行其后的代码。4)catch中的异常类型如果没有父子类关系,则谁声明在上
,谁声明在下无所谓。catch中的异常类型如果满足父子类关系,则要求子类一定声明在父类的上面。否则,报错。5)finally中编写
的代码一定会被执行,主要用于将try中生成的资源对象进行释放,如输入输出流、数据库连接等。6)程序继续执行。有了try-catch
-finally异常处理的保护,代码的健壮性得以增强,避免了代码异常导致的程序中断退出。【例8-12】前面几个运行时异常的异常处理
。程序在JDK17下编译成功,运行如下所示:c:\工作目录>java ProcessRunTimeExceptions数组下标越界
异常:Index 7 out of bounds for length 6数字格式异常:For input string: "ok
"空指针异常:Cannot load from char array because "" is null算数异常
即除0异常:/ by zero上述几个运行时异常都被处理了,程序不会异常退出!异常处理成功,程序继续执行...上面几个异常,只要有
一个没被处理,程序将在引发异常处即中断退出。然而,运行时异常在实际编程过程中,往往会由于程序员的疏忽或者程序规模较大较难掌控而引发
,对于程序运行一次就会引发的异常,程序员可以立刻查找原因,对引发异常的代码进行异常处理,而对于其他一些异常,可能要运行多次,才会发
现有异常,因此,程序员在交付程序前,务必要多测试几次,关于软件测试,也是一门学问,将来,大家如果参与实际项目开发了,就会体会到。【
例8-13】主动抛出异常并进行异常处理。public class LoginException { public st
atic void login(String user,String pwd) { if(user==null||p
wd==null) throw new NullPointerException("用户名或者密码为空");
//主动抛出异常 //... } public static void main(String[] ar
gs) { try { String user = null; St
ring pwd = null; login(user,pwd); } catch (
Exception e) { System.out.println(e.getMessage()); //
getMessage获得的信息见运行输出 } }}程序编译、运行输出如下:c:\工作目录>
java LoginException用户名或者密码为空8.8.3 异常类Java类库自带定义了很多异常类,它们的根类是Thro
wable,如下图所示:1. 库类异常类Java库类中的异常类根类是Throwable,由它派生出了两个子类:Exception类
和Error类。Throwable类提供了三个非常有用的方法:(1)String getMessage():获取异常的描述信息;(
2)String toString():获取异常的类型、异常描述信息;(3)void printStackTrace():打印异常
的跟踪栈信息并输出到控制台,但不能在System.out.println()中使用该方法;其中包含了异常的类型、异常的原因、异常出
现的位置;在开发和调试阶段,该方法很有用,方便调试和修改;由于Throwable是根类,它的子类Exception类和Error类
及它们再派生出的所有子类,都继承了该方法。【例8-15】Throwable类的三个常用方法。public class Proces
sRunTimeExceptions1{ public static void main(String[] args) {
short x[ ] = new short[6]; try { System.out
.println(x[7]); }catch (ArrayIndexOutOfBoundsException
e){ System.out.println(e.getMessage()); Sys
tem.out.println(e.toString()); System.out.println("异常栈
追踪:"); e.printStackTrace(); } }}编译,运行如下:c:\
工作目录>java ProcessRunTimeExceptions1Index 7 out of bounds for leng
th 6 // e.getMessage();java.lang.ArrayIndexOutOfBoundsExceptio
n: Index 7 out of bounds for length 6 // e.toString();异常栈追踪://下面是
e.printStackTrace();java.lang.ArrayIndexOutOfBoundsException: Ind
ex 7 out of bounds for length 6 at ProcessRunTimeException
s1.main(ProcessRunTimeExceptions1.java:5) //5是代码出错行Error类及其子类属于系统
错误,不能通过try-catch异常处理加以解决,它们是Java虚拟机也无法解决的,如JVM系统内部错误、资源耗尽等严重错误,Er
ror子类VirtualMachineError又派生了StackOverflowError和OutOfMemoryError等子
类错误。【例8-16】StackOverflowError示例。public class StackOverflowErrorEx
ample { public static void main(String[] args) { //栈溢出错误:java.
lang.StackOverflowError main(args); //无限递归导致 }}编译成功,但运行如下:c:\
工作目录>java StackOverflowErrorExampleException in thread "main" jav
a.lang.StackOverflowError at StackOverflowErrorExample.mai
n(StackOverflowErrorExample.java:4) //4是代码出错行 at StackOver
flowErrorExample.main(StackOverflowErrorExample.java:4) at
StackOverflowErrorExample.main(StackOverflowErrorExample.java:4)
…… ……c:\工作目录> // 程序出错被中断退出【例8-17】OutOfMemoryErr
or示例。public class OutOfMemoryErrorExample { public static void m
ain(String[] args) { //堆溢出:java.lang.OutOfMemoryError Long
[] arr = new Long[102410241024]; //索要8G字节空间导致 }}编译成功,但运行如下:c:\
工作目录>java OutOfMemoryErrorExampleException in thread "main" java.
lang.OutOfMemoryError: Java heap space //Java堆空间 at OutOfM
emoryErrorExample.main(OutOfMemoryErrorExample.java:4) //4是出错代码行/
/ 程序出错被中断退出c:\工作目录>从上可见,对于Error类错误,程序都将被中断而退出,而且Error类错误是无法用异常处理解
决的,即即使程序员用try-catch去捕获处理也无济于事,只能靠程序员查错修改源代码,方能解决。我们通常说的异常主要是指Exception,类Exception又派生出运行时异常RuntimeException和其他(库类)异常,如IOException和SQLException等,除RuntimeException以外的其他(库类)异常,程序员用户必须进行处理,即进行抛出异常声明和捕获异常处理,虽然只进行抛出异常声明,程序也能编译通过,但一旦引发异常,由Java虚拟机来处理,那么程序也就被中断退出了,所以,要让程序能抵御异常保持运行,程序员就必须采用try-catch来捕获处理异常。其他(库类)异常如IOException和SQLException等有Java编译系统的报错提醒,让程序员省心不少,但RuntimeException就没这个待遇了,因为它们往往无法预期,在编译阶段是查不出来的,只有当程序运行时,运行时异常才会出现。一旦发现程序有运行时异常,程序员就必须进行纠错,要么直接修改出错源代码,要么对引发异常的源代码进行try-catch处理。2. 自定义异常类由上图8-3可见:程序员用户可以自定义异常类,它们继承自Exception类,所以可以使用父类的各种方法,程序员用户也可以自己编写方法来处理特定的事件。不过,在实际开发中,自定义异常类常直接从RuntimeException类继承,因为用户自定义异常类一般都属于运行时异常,这样定义,也可以使用更多的父类方法。【例8-18】用户自定义异常类示例。处理异常的原则自定义异常建议从RuntimeException类继承,且要尽量避开已存在的异常;异常只能用于非正常情况,try-catch的存在会影响程序性能,应尽量缩小try-catch的代码范围;在循环之外使用try-catch,不宜在循环体中进行异常处理;尽量避免常见运行时异常的出现,如NullPointerException等。8.9 小 结本章简单介绍了进程和线程的区别,阐述了多线程的基本概念以及创建多线程程序的两种方法和应用实例,讲解了线程的不同状态之间的转换关系和调用方法;接着,又进一步讲述了控制线程的一些基本方法、线程的调度策略以及优先级的定义;然后介绍了守护线程和线程组的相关知识;最后,对Java的异常机制进行了简要介绍。
献花(0)
+1
(本文系大高老师首藏)