目录
1.异常处理
异常是一个事件,当执行中的程序中断其正常的指令流时出现,Java代码能检测出错误,向运行系统指明是什么错误,抛出一个异常。通常,抛出的事件使线程终止,显示其错误信息。如果想自己处理异常,可以用一个catch语句捕获异常。
1.1.异常
异常是异常事件的简称,许多不同的错误都会导致异常,如硬盘错误,编程错误等。若这些错误出现在java方法中,该方法创建一个异常对象,对象中包含异常类型、错误出现时程序的状态等信息,交到运行系统,这叫抛出一个异常。
运行系统负责找出处理错误的方法,它往回搜索方法调用栈,直到找出一个合适的异常处理器,所谓合适,是指抛出的异常类型与异常处理器处理的类型相同。选择异常处理器称为捕获异常,若运行系统找不到合适的异常处理器,系统就终止运行。
异常必须是Throwable型,即是Throwable类与子类的实例,Throwable类在java.lang包中定义。Throwable是object的子类,它有Error和Exception两个子类。
一、Error是动态链接失败或虚拟机出现“硬”失效后出现的,由虚拟机抛出。普通的java程序不会抛出Error,也不需要捕获。
二、Exception表明出现了问题,但不是严重的系统问题。大多数程序都可以抛出或捕获它。Java包中定义了许多Exception的后继类,指明各种类型的异常。
Exception可分为运行时刻异常(RuntimeException)和非运行时刻异常(Non-runtime)。
1、运行时刻异常RuntimeException是Exception的一个重要子类,这种异常在程序中数量很多,检查它们所花的代价超过了捕捉和声明带来的好处,因此编译器不要求捕捉或声明运行时刻异常。
2、非运行时刻异常Non-runtime代表合法操作所调用方法必须知道的有用信息,如磁盘满了、没有访问权限等,非运行时刻异常必须抛出或捕捉。
1.2.捕获与声明的要求
Java语言要求 各方法 捕获与声明 在方法的作用范围内可能抛出的所有非运行时刻异常。如果编译器检测到某个方法没有满足要求,它会显示出错信息,并拒绝编译程序。
例如,源程序中有一句:while(System.in.read()!=-1) 用到了I/O操作read(),可能会出现IOException,如果程序中没有catch部分,就要在方法的声明部分用throws子句说明可以抛出它,如:public static void main(String[] args) throws java.io.IOException{...} 这样编译程序才认可,否则会出现出错信息。
为什么方法不捕获异常时就要声明会抛出异常?因为方法抛出的任何异常实际上是该方法的公共编程接口的一部分,方法的调用者必须知道该方法抛出的异常,以便决定如何处理这些异常。
1.3.处理异常
异常示例:
import java.io.*;
import java.util.Vector;
class ListofNumbers{
private Vector<Integer> victor; //创建向量victor
final int size=10;
public ListofNumber(){ //构造方法
int i;
victor=new Vector<>(size); //设定向量victor的容量
for(i=0;i<size;i++)
victor.addElement(new Integer(i)); //在victor末尾添加对象
}
public void writeList(){
PrintStream pStr=null;
System.err.println("Entering try statement");
int i;
pStr=new PrintStream(new BufferedOutputStream(new FileOutputSream("OutFile.txt")));
for(i=0;i<size;i++)
pStr.println("Value at:"+i+"="+victor.elementAt(i)); //写入OutFile.txt
pStr.close();
}
public static void main(String[] args){
ListOfNumbers lofn=new ListOfNumbers();
lofn.writeList();
}
}
该类可能出现两个异常,第一个是IOException,由FileOutputStream("OutFile.txt")构造方法打不开该文件时抛出;第二个是ArrayIndexOutOfBoundsException,由victor.elementAt(i)遇到过大或过小的i时抛出。编译时编译器会指出要抛出IOException异常的信息,但不会指出要抛出ArrayIndexOutOfBounds异常,因为前者是非运行时刻异常,后者是运行时刻异常。
建造一个异常处理器处理上述异常,由try块、catch块、finally块三个部分组成。
1.3.1.try块
建造异常处理器的第一步是把会出现异常的Java语句放入try块,由try块管理所包含的语句,定义异常处理器的作用范围。try后面必须跟随一个或多个catch块,或一个finally块。
1.3.2.catch块
catch块的格式为:
catch(SomeThrowableClassName variableName){...}
SomeThrowableClassName定义了异常处理器可处理的异常类型,且必须是Throwable类的后继类。参数名variable是处理器用于引用异常对象。假设异常名是e,就可以用e调用异常的实例变量和方法,如e.getMessage()得到错误的其他信息,getMessage()是Throwable类提供的方法。
catch中的Java语句是调用异常处理器时执行的,当异常类型与catch参数类型匹配时,运行系统就调用异常处理器。
对于本例,可以设置两个catch块,构造两个异常处理器:
try{
···
}catch(ArrayIndexOutOfBoundsException e){
System.err.println("Caught ArrayIndexOutOfBoundsException:"+e.getMessage());
}catch(IOException e){
System.err.println("Caught IOException:"+e.getMessage());
}
catch块的顺序就是运行系统检查异常处理器的顺序,所以注意排序。
Java语言允许编写处理多种类型异常的通用处理器。对于本例,可以设置一个catch块,构造一个通用的异常处理器:
catch(ArrayIndexOutOfBoundsException|IOException e){
System.err.println("Caught Exception:"+e.getMessage());
}
但是,捕获太多异常的处理器对错误恢复没有帮助,因为无法知道具体发生了哪类异常。
1.3.3.finally块
设置异常处理器的最后一步是提供一种机制清除方法的状态,把控制传给程序的其他部分。这种机制由finally块实现。 不管try块发生了什么事情,运行系统总会执行finally块的语句。
对于本例,不管发生了哪种异常,打开的输出流PrintStream总要关闭,所以finally执行这部分功能;如果没有finally块,万一出现了运行时刻异常而没有适合的处理器,PrintStream就无法关闭。
下面是处理异常示例,finally块代码不再赘述:
import java.io.*;
import java.util.Vector;
class ListofNumbers{
private Vector<Integer> victor; //创建向量victor
final int size=10;
public ListofNumber(){ //构造方法
int i;
victor=new Vector<>(size); //设定向量victor的容量
for(i=0;i<size;i++)
victor.addElement(new Integer(i)); //在victor末尾添加对象
}
public void writeList(){
PrintStream pStr=null;
try{
System.err.println("Entering try statement");
int i;
pStr=new PrintStream(new BufferedOutputStream(new FileOutputSream("OutFile.txt")));
for(i=0;i<size;i++)
pStr.println("Value at:"+i+"="+victor.elementAt(i)); //写入OutFile.txt
}catch(ArrayIndexOutOfBoundsException e){
System.err.println("Caught ArrayIndexOutOfBoundsException:"+e.getMessage());
}catch(IOException e){
System.err.println("Caught IOException:"+e.getMessage());
}finally{
if(pStr!=null){
System.err.println(Closing PrintStream);
pStr.close();
}else{
System.err.println(PrintStream not open);
}
}
}
public static void main(String[] args){
ListOfNumbers lofn=new ListOfNumbers();
lofn.writeList();
}
}
1.3.4.新形式的try块语句
在上例中,当文件使用完之后,在finally字句中显式地调用了close()来关闭文件。从JDK7开始,Java增加了一种新的异常处理机制用以自动关闭文件或释放资源,这种机制通过一种新形式的try块语句(try-with-resources)来实现。
格式为:try(resource-specification){···} 其中,resource-specification用来声明并初始化文件或资源,若是多个文件或资源,需用分号隔开。当try块结束时,资源会自动释放,文件也会自动关闭,无须再调用close()语句。
上例可以改写为:
try(PrintStream pStr=new PrintStream(new BufferedStream(new
FileOutputStream("OutFile.txt")))){
int i;
System.err.println("Entering try statement");
for(i=0;i<size;i++)
pStr.println("Value at:"+i+"="+victor.elementAt(i)); //写入OutFile.txt
}catch(ArrayIndexOutOfBoundsException e){
System.err.println("Caught ArrayIndexOutOfBoundsException:"+e.getMessage());
}catch(IOException e){
System.err.println("Caught IOException:"+e.getMessage());
}
其中,pStr是try块语句中final类型的局部变量。try块结束时,与pStr关联的文件会被自动关闭。
1.4.抛出异常
任何Java代码都可以抛出异常,通常有两种方式:throws语句或throw语句。
1.4.1.throws子句
如果在一个方法中生成了一个异常,但该方法并不想处理或是不清楚该如何处理,就可以在方法声明部分用throws语句抛出这个异常对象:throws ExceptionObject;ExceptionObject是Throwable类的任何子类的实例,如果不是,编译器会提示错误信息。
对于上述例子,如果不想编写异常处理器,可以在writeList()方法的声明中抛出异常:
public void writeList() throws IOException,ArrayIndexOutOfBoundsException{
···
}
然而,如果writeList()方法声明将其抛出不作处理,那么调用该方法的main()方法就必须处理该异常:
public static void main(String[] args){
ListOfNumbers lofn=new ListOfNumbers();
try{
lofn.writeList();
}catch(IOException e){
System.err.println("Caught IOException:"+e.getMessage());
}
}
1.4.2.throw子句
在方法体可以使用throw语句抛出异常,例如:if(size=0)throw new EmptyStackException();
这是由程序自己抛出异常。
1.5.创建自己的Exception类
当设计一个包,提供一些有用的类给用户,必须花时间设计一些类会抛出的异常。
关于异常有几个规则:
1、通常方法应抛出Exception,派生Exception的子类。只有虚拟机错误才属于RuntimeException。
2、一个方法可以抛出虚拟机运行时的错误RuntimeException,但由虚拟机检查并抛出它更容易。
例如,编写链表(linked list)类,其中有三个方法:
1、addElement(Object o)——向列表中添加元素,当元素个数超出列表长度时抛出异常。
2、getElememt(int n)——返回列表中第n个位置的元素,若n小于0或大于列表长度会抛出异常。
3、FirstElement()——返回列表中第一个对象,若列表中无任何对象会抛出异常。
若Java运行系统没有提供上述异常类,就必须编写它们。可以设计一个LinkedListException代表链表类抛出的所有可能异常,用户可以用一个异常处理器:
catch(LinkedListException){··}捕获这些异常,也可以为上面三种异常各编写一个异常处理器。
LinkedListException必须是Throwable对象,而程序中的抛出对象又属于Exception类,所以可将LinkedListException作为Exception类的子类。
class LinkedListException extends Exception{
private static final long serialVersionUID=1L;
public LinkedListException(){
super();
}
public LinkedListException(String s){
super(s);
}
}
class List{
Object[] items;
int size=0;
public List(int len){
items=new Object[len];
}
public void addElement(Object o) throws LinkedListException{
if(size==items.lenght)throw LinkedListException("列表已满!");
else{
items[size]=o;
size++;
}
}
public Object getElement(int n) throws LinkedListException{
if(n<0||n>items.length)throw LinkedListException("列表下标越界!");
else return items[n];
}
public Obeject FirstElement() throws LinkedListException{
if(items.length==0)throw LinkedListException("列表中无任何对象!");
//items.length==0感觉有点问题
else return items[0];
}
}
public class myLinkedList{
public static void main(String[] args){
List list=new List(2);
try{
list.addElement("广东");
list.addElement("浙江");
list.addElement("山东");
}catch(LinkedListException e){
System.err.println("err:"+e.getMessage());
}
try{
System.err.println(list.getElement(2));
}catch(LinkedListException e){
System.err.println("err:"+e.getMessage());
}
try{
System.err.println(list.FirstElement());
}catch(LinkedListException e){
System.err.println("err:"+e.getMessage());
}
}
}
//结果:
//err:列表已满
//err:列表下标越界
//广东
2.并发
并发机制机制支持程序员编写可以同时执行多个任务的应用程序,Java平台的编程语言和类库都支持基本并发机制。
2.1.线程
线程是程序中单个顺序控制流,有时又称为执行上下文或轻量级进程。线程有起点、终点和顺序,但它不能单独运行,而要在程序中运行。Java的重要优点之一就是它可以在一个程序中同时运行多个 执行不同任务的 线程,避免了在单线程的情况下一个任务未完就不能执行另一个任务的现象。
Java在编程语言级就具有支持多线程的能力,有两种方式可以创建多线程的类:
1、派生Thread类的子类,Thread类在java.lang包中定义,它实现了Runnable接口。
2、创建实现Runnable接口的类,Runnable接口也在java.lang包中定义,接口中定义了一个run()方法。
为了编写用户的线程,必须了解线程的属性:
2.1.1.线程体
线程的各种活动都发生在线程体中,线程体在run()方法中编写。有两种方式可以构造用户的线程体。
1、重写Thread类的子类中的run()方法,例如:
class className extends Thread{
public void run(){
···
}
}
2、重写Runnable接口的run()方法,例如:
class className implements Runnable{
public void run(){
···
}
}
多线程编程主要是为线程编写run()方法。如果编写的类必须从其他类导出,则选用第二种方法,因为java不支持多重继承,继承了其他类后不能再继承Thread类,只能利用Runnable接口。
编写线程体示例:
class AThread extends Thread{
public AThread(String str){
super(str);
}
public void run(){
for(int i=0;i<10;i++){
System.out.println(i+" "+getName());
try{
sleep((int)(Math.random()*1000)); //休眠0到1000毫秒/1秒的随机时间间隔
}catch(InterruptedException e){
System.out.println(e.getMessage());
}
}
System.out.println("END!"+getName());
}
}
class MyThread{
public static void main(String[] args){
new AThread("Win").start();
new AThread("Lost").start();
}
}
2.1.2.线程的状态
线程与进程一样,都有自己的生命周期,分为新进程态、可运行态、不可运行态和死亡态。(线程状态好像有说6种的)
1、新线程态
当用new运算符创建了一个线程,只要未调用start()方法,就是新线程态,此时线程只是空的对象,没有分配任何系统资源,用stop()方法可以把新线程态变为死亡态。
2、可运行态
当调用start()方法启动新线程,它就变成可运行态。启动后,该线程获得了必要的系统资源,并调用线程的run()方法。这种状态不等于运行,只相当于就绪,任何时刻只有一个线程在运行。
3、不可运行态
当调用了suspend()方法、sleep()方法或者是调用wait()方法等候一个条件变量或被I/O阻塞,线程就从可运行态变成不可运行态。即使处理器有空闲时间,也不能执行不可运行态的线程。要恢复可运行态,只有消除原来不可运行的原因。 如被suspend()挂起的线程再调用resume()方法恢复可运行态,sleep()时间已结束,所等候的条件变量的拥有对象调用了notify()或notifyAll()方法,输入输出命令已经完成等。
4、死亡态
线程可以自然死亡,如它的run()方法正常退出运行;也可以被杀死,如调用stop()方法,stop()方法抛出一个ThreadDeath对象给线程,线程收到ThreadDeath异常才死。
注意:在Thread类中,suspend()、resume()和stop()等方法不是线程安全的,不建议使用。
Thread类有个isAlive()方法可以检查线程是否活着,若线程已经开始,还没有停止,处于可运行态或不可运行态,isAlive()就返回true;若处于新线程或死亡态,就返回false。
线程状态示例:
import java.applet.Applet;
import java.awt.Graphics;
import java.util.*;
public class Clock extends Applet implements Runnable{
private static final long serialVersionUID=1L; //定义程序序列化ID
Thread clockThread;
public void start(){
if(clockThread==null){
clockThread=new Thread(this,"Clock");
clockThread.start();
}
}
public void run(){
while(clockThread!=null){
repaint();
try{
Thread.sleep(1000);
}catch(InterruptedException e){
System.out.println(e.getMessage());
}
}
}
public void paint(Graphics g){
Calendar now=new GregorianCalendar();
g.drawString(now.get(Calendar.HOUR_OF_DAY)+":"+now.get(Calendar.MINUTE)+
":"+now.get(Calendar.SECOND),5,10);
}
}
2.1.3.线程优先级
在单个cpu上运行多个线程需要调度,java运行系统支持简单的确定性调度算法,称为固定优先级调度,选择各可运行态的线程中优先级最高的先运行,同级线程则排队执行。
Java线程的优先级是继承 创建它的线程而来的,可用SetPriority()方法修改,范围在1到10之间,数字越大,优先级越高。Thread类的常量MIN_PRIORITY、NORM_PRIORITY、MAX_PRIORITY分别代表优先级1、5、10。一个线程的默认优先级为5,可以用getPriority()方法获取一个线程的优先级。
Java运行系统不为同级的线程分配时间片,所以不需要编写依赖时间片调度的程序。若一个线程调用yield()方法暂停执行从而放弃CPU的资源调用,同级的线程可以有机会运行,调度程序按排队调下一个线程运行。如果其他可运行线程优先级低,则yield()方法不起作用。
2.1.4.Daemon线程
Daemon线程专为同一进程中的其他线程或对象提供服务,它是一个独立的线程,其run()方法通常是一个无限循环,等候服务请求。
为了定义一个线程为Daemon线程,要调用SetDaemon()方法,并令布尔参数为true,isDaemon()判断一个线程是否为Daemon线程。
2.1.5.线程组
Java的所有线程都是线程组的成员,线程组用java.lang包中的ThreadGroup类来实现。线程组提供一种机制将多个线程收集成为一个对象,在组中一起处理它们。
要建立线程组,必须在创建时进行,创建组后,线程不能换到另一组。Thread类有三个构造方法设置线程组:
1、Thread(ThreadGroup,Runnable)
2、Thread(ThreadGroup,String)
3、Thread(ThreadGroup,Runnable,String)
例如:Thread myThread=new Thread(myThreadGroup,"my group");
其中myThreadGroup是一个ThreadGroup类对象。
如果不显式为一个线程设置线程组,则系统自动把它放在创建它的进程的同一组,称为当前线程组。
2.1.6.ThreadGroup类
ThreadGroup类可以包含任意线程,也可以包含其他ThreadGroup,形成树状的线程与线程组结构。ThreadGroup类有一些方法允许修改组中所有线程的当前状态,如resume()恢复线程、stop()停止线程,suspend()挂起线程,这些方法会改变组中每个线程和子组中所有线程的状态。
2.2.同步与锁定
2.2.1.监视器
竞争条件:多个线程异步地执行,试图同时访问同一个对象,因此出现错误结果。
Java通过条件变量解决竞争条件问题,条件变量指的应该就是被访问的那个对象,针对条件变量采用监视器同步各线程。监视器通常提供一个锁,获得锁的线程进入监视器,直到被锁的线程退出监视器,等待的线程才有机会进入监视器。
关键部分:程序中能使并行线程访问共同数据对象的代码段。
Java语言用synchronized关键字标识关键部分,而关键部分通常在方法里,标有synchronized的方法称为同步方法。
每一个同步方法对应唯一一个监视器,当一个对象调用同步方法时,它首先试图获得监视器,锁定自己,防止其他对象调用同步方法,等到同步方法返回,这个对象才释放监视器并解锁。
获得监视器和释放监视器都由Java运行系统自动完成。
线程同步示例:
class Producer extends Thread{
private Store store;
public Producer(Store s){
store=s;
}
public void run(){
for(int i=0;i<10;i++){
store.put(i);
System.out.println("Producer:"+i);
try{
sleep((int)(Math.random()*100));
}catch(InterruptedException e){System.out.println(e.getMessage());}
}
}
}
class Comsumer extends Thread{
private Store store;
public Comsumer(Store s){
store=s;
}
public void run(){
int value=0;
for(int i=0;i<10;i++){
value=store.get();
System.out.println("Comsumer:"+value);
}
}
}
public class MyProCon{
public static void main(String[] args){
Store s=new Store();
Producer p1=new Producer(s);
Comsumer p2=new Comsumer(s);
p1.start();
p2.start();
}
}
class Store{
private int sep;
private boolean available=false;
public synchronized int get(){
while(available==false){
try{
wait();
}catch(InterruptedException e){System.out.println(e.getMessage());}
}
available=false;
notify();
return sep;
}
public synchronized void put(int value){
while(available==true){
try{
wait();
}catch(InterruptedException e){System.out.println(e.getMessage());}
}
available=true;
sep=value;
notify();
}
}
2.2.2.wait()方法
该方法使当前线程进入不可运行状态,线程将持续等待到其他线程调用notify()或notifyAll()方法来通知它。当线程处于wait()状态,会自动释放监视器,当它退出等待状态,又重新获得监视器。
2.2.3.notify()与notifyAll()方法
notify()方法唤醒了一个调用了当前对象的wait方法而进入等待状态的线程,notifyAll()通知全部进入等待状态的线程。