异常和多线程

来源:互联网 发布:php网页获取qq号码 编辑:程序博客网 时间:2024/06/05 22:56

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流------

一.异常

1.1异常概述

a.java的异常体系为Throwable,其下有两个子体系,分别为Exception,Error,其中前者为可处理的异常。

b.定义任何一个非main方法(功能)都是供其他的方法调用的,java中封装了很多最底层的方法,这些最底层的方法就是供我们自定义方法时调用的,其实任何一个自定义的方法都是由java中的标示符(用来给类、对象、方法、变量、接口和自定义数据类型命名的)和关键字组成的,方法只有在被调用时才有可能产生异常,这是因为调用方法时会传入实参值,如果出现传入的实参值虽然符合形参的数据类型,但是却不合常理的情况,我们就认为该方法在调用时出现了异常,所以我们在定义一个方法时,如果存在着这样一种情况:该方法被调用时传入的实参值虽然符合形参的数据类型但却不合常理。我们就必须在定义方法时就考虑形参在取哪些实际值时是不合理的,即假设形参取到了这些不合常理的实际值时,我们该怎么办,java中给我们的解决办法就是抛出(throw)一个或多个带异常信息的异常对象,如果该异常对象的类型不是RuntimeException,还必须在方法中依次声明(throws)这些异常对象的类型。其他方法在调用该方法(传入实参)时,可能会因为传入的实参不合理而接到该方法抛出的一个或多个异常对象,调用方法有两种处理方式:一是把这些异常对象继续抛出;二是使用try~catch代码块处理(try{......}代码块负责检测出这些这些异常对象,多个catch(Exception e){......}代码块按照先接收子类异常对象的顺序负责分别接收这些异常对象并做相应的处理)。try{.....}代码块中的首行代码通常是可能产生异常对象的代码(即是调用了某个抛异常对象的方法),剩余的代码则是一旦抛异常就没有必要在执行的代码,而对于无论是否抛异常都要执行的代码则放在finally{.....}代码块中。

c.如果某个方法可能抛出的异常对象的类型是RuntimeException,则其他方法调用该方法时一律将异常对象继续抛出且不用使用throws关键字声明抛出的异常对象的类型,最终异常对象被抛给了虚拟机,虚拟机对异常的处理就是停止整个程序的继续运行,并打印出异常信息。这么做的目的是告诉主函数main在调用某个可能抛出异常对象的方法时,传入的实参有问题,需要重新传入。

1.2自定义异常

a.自定义异常会继承父类异常的一个属性,该属性用于描述异常,在构造方法中初始化异常对象时就要给该属性赋值,出现异常并处理时,可以打印出该属性值(System.out.println(e.toString())或者e.printStackTrace()),自定义异常中的toString()方法也是继承自父类异常。

public class MyException extends RuntimeException{MyException(String message){super(message);}}

public class ExceptionDemo {public static int div(int a,int b){if(b==0)throw new MyException("除数为零啦");if(b<0)throw new MyException("除数为负数啦");return a/b;}public static int method(int a,int b){return div(a,b);}public static void main(String[] args){System.out.println(method(2,0));}}

二.多线程

2.1多线程概述

a.一个正在执行的程序就是一个进程,一个进程由一个或多个线程组成,每个线程对象都有自己的执行代码(放在run()方法体内),CPU同时运行一个或多个程序是通过在多个线程之间快速切换实现的,也即运行一个线程对象的一部分执行代码后快速切换运行另一个线程对象的一部分执行代码,由于切换速度非常快,所以看上去多个程序可以同时运行。

b.java也是支持多线程的,java.exe程序在运行时就开启了两个线程,一个是主线程(执行主函数main中代码),另一个就是垃圾回收线程(处理main函数中产生的垃圾对象),如果在main函数中出现多部分代码可以与剩余的代码同时执行,则可以把每部分代码封装到run()方法中,将该方法定义在一个线程类(该线程类继承Thread类)中,也可以在线程类中再定义一些属性,在run()中很可能会用到这些属性,属性可以定义为静态显示初始化的(例如多个售票员同时售票,车票的初始总张数就可以定义为静态的,存放在方法区中),也可以定义为非静态的,然后通过构造方法进行初始化(也即一创建线程对象就可以给该属性赋值,再调用start()方法执行run()方法时,方法体内用到的属性就有了相应的初始值),例如若不使用线程对象的默认名字(Thread.CurrentThread.getName()),也可以定义一个名字属性,这样创建每个线程对象时都可以给它取名字。另一种情况是:一个类中的某些方法(功能)具有多线程的特性(例如售票员的售票功能,多个售票员是可以同时售票的,这也是提高工作效率的一种方式),我们就可以让该类实现Runnable接口,重写接口中的run()方法,把具有多线程特性的功能代码放到run()方法中即可。这样创建多个线程对象(多个线程对象执行的代码相同,即共用类对象run()方法中的代码,若类对象中还定义了属性(该属性可以是引用变量类型),则多个线程对象的run()方法操作的都是该属性,即多个线程对象操作同一数据(对象),存在着线程安全问题)时首先创建该类对象,再把该类对象作为参数传递给Thread类的构造方法Thread(),相当于通过构造方法给Thread类对应的线程对象的一个属性初始化,该属性就是一个实现了Runnable接口的类对象,这样调用线程对象的start()方法便可以执行类对象中的run()方法。在实际开发中更多的是使用Runnable接口来实现多线程。

2.2线程对象的状态

a.主线程中的垃圾对象是通过垃圾回收线程处理的,而线程对象(包括主线程)则存在着自然消亡的过程,也即当线程对象中的run()方法中的代码执行完毕,线程对象也就自然消亡了,在线程对象消亡之前,线程对象存在着以下几个状态:

(1)阻塞(临时)状态,创建好线程对象后,调用其start()方法之后线程就处于阻塞状态,等待着CPU执行其run()方法中的代码,即有运行的资格但是没有获取到执行权,在run()方法中添加代码Thread.yield();当CPU执行到改行代码时,当前线程自动短暂释放CPU执行权而处于阻塞状态。

(2)运行状态,当CPU开始执行线程对象run()方法中的代码时,该线程对象就处于运行状态,执行run()方法中的一部分代码后,CPU很快就切换执行其他线程对象的run()方法中代码,此时当前线程对象又处于了阻塞状态。

(3)冻结状态,在run()方法某行调用sleep(long time),wait(),join()方法时,其对应的线程对象就处于冻结状态,即本来线程对象获取到了CPU执行权开始执行其run()方法中的一部分代码,当执行到调用sleep(long time)或wait()方法行代码时,当前线程对象放弃当前CPU执行权并暂时放弃运行资格,特别注意,若run()方法调用了其他的方法,则也可以在其他方法中调用sleep(long time)或wait()方法,调用sleep(long time)方法是通过Thread.sleep(long time)(即sleep(long time)方法是Thread类中的静态方法),当休眠时间一到,其对应的线程对象又有了运行资格而处于阻塞状态;调用wait()方法后(是通过引用变量名或this调用,因为在Object类中就定义了wait()方法,任何对象都有该方法),线程对象处于冻结状态,直到使用notify()方法唤醒该线程对象后,该线程对象又处于阻塞状态;例如在主线程中创建线程对象t并调用t.start()方法,然后再调用t.join()方法,当主线程执行到该行代码时,主线程就处于冻结状态,等到线程对象t执行完所有代码而自然消亡之后,主线程才又回到阻塞状态,同样的在run()方法中调用t.join()方法也是如此。

2.3多线程安全问题

a.以火车站售票为例,多个窗口同时售票,代码如下:

public class TicketPerson implements Runnable{private int ticketCount=100;public void run(){while(true){if(ticketCount>0){System.out.println(Thread.currentThread().getName()+":"+ticketCount--);}}}public static void main(String[] args) {TicketPerson tp=new TicketPerson();Thread t1=new Thread(tp);Thread t2=new Thread(tp);Thread t3=new Thread(tp);Thread t4=new Thread(tp);Thread t5=new Thread(tp);t1.start();t2.start();t3.start();t4.start();t5.start();}}

上述代码是可能出现线程安全问题的,例如当ticketCount=1时,一个线程刚执行完if(ticketCount>0),CPU马上切换执行另一个线程,该线程把run()方法中的两条语句都执行了,此时ticketCount=0,当CPU在切换执行之前那个线程时,就会执行run()方法中的第二条语句,并让ticketCount=-1,也就出现了线程安全问题了,根本原因是多个线程,多条语句操作共享数据ticketCount,我们可以把多条语句融合为一条语句,只需把多条语句放到一个同步代码块中即可;也可以使用同步函数把多条语句放到该同步函数中,此时的锁对象是调用该同步函数的对象tp,代码如下:

public class TicketPerson implements Runnable{private int ticketCount=100;public void run(){Object obj=new Object();while(true){synchronized(obj){if(ticketCount>0){System.out.println(Thread.currentThread().getName()+":"+ticketCount--);}}}}public static void main(String[] args) {TicketPerson tp=new TicketPerson();Thread t1=new Thread(tp);Thread t2=new Thread(tp);Thread t3=new Thread(tp);Thread t4=new Thread(tp);Thread t5=new Thread(tp);t1.start();t2.start();t3.start();t4.start();t5.start();}}

public class TicketPerson implements Runnable{private int ticketCount=100;public void run(){while(true){show();}}public synchronized void show(){if(ticketCount>0){System.out.println(Thread.currentThread().getName()+":"+ticketCount--);}}public static void main(String[] args) {TicketPerson tp=new TicketPerson();Thread t1=new Thread(tp);Thread t2=new Thread(tp);Thread t3=new Thread(tp);Thread t4=new Thread(tp);Thread t5=new Thread(tp);t1.start();t2.start();t3.start();t4.start();t5.start();}}

b.使用同步等待唤醒机制,例如创建两个线程负责向堆内存中写入数据(其实就是创建Person对象,在run()方法中调用其set()方法不断给Person对象的属性赋值),创建两个线程负责及时取走堆内存中的数据,也即写数据线程每写一次数据,读数据线程就读取一次数据。

public class Person {private String name;private int age;boolean flag=false;public synchronized void set(String name,int age){while(flag)try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}this.name=name;this.age=age;System.out.println(Thread.currentThread().getName()+":"+this.name+","+this.age);flag=true;this.notifyAll();}public synchronized void get(){while(!flag)try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+":"+"name="+name+";"+"age="+age);flag=false;this.notifyAll();}public static void main(String[] args) {Person p=new Person();SetThread s1=new SetThread(p);SetThread s2=new SetThread(p);GetThread g1=new GetThread(p);GetThread g2=new GetThread(p);s1.start();s2.start();g1.start();g2.start();}}class SetThread extends Thread{private Person p;SetThread(Person p){this.p=p;}public void run(){int count=1;while (true){p.set("zhangsan"+count++, count);}}}class GetThread extends Thread{private Person p;GetThread(Person p){this.p=p;}public void run(){while (true){p.get();}}}

c.上述同步等待唤醒机制是基于Object中的监视器方法:wait(),notify(),notifyAll(),下面介绍使用Lock接口优化同步等待唤醒机制,代码如下:

public class Person {private String name;private int age;private boolean flag=false;private Lock lock=new ReentrantLock();private Condition setCondition=lock.newCondition();private Condition getCondition=lock.newCondition();public void set(String name,int age){lock.lock();try {while(flag)setCondition.await();this.name=name;this.age=age;System.out.println(Thread.currentThread().getName()+":"+this.name+","+this.age);flag=true;getCondition.signal();} catch (InterruptedException e) {e.printStackTrace();}finally{lock.unlock();}}public void get(){lock.lock();try {while(!flag)getCondition.await();System.out.println(Thread.currentThread().getName()+":"+"name="+name+";"+"age="+age);flag=false;setCondition.signal();} catch (InterruptedException e) {e.printStackTrace();}finally{lock.unlock();}}public static void main(String[] args) {Person p=new Person();SetThread s1=new SetThread(p);SetThread s2=new SetThread(p);GetThread g1=new GetThread(p);GetThread g2=new GetThread(p);s1.start();s2.start();g1.start();g2.start();}}class SetThread extends Thread{private Person p;SetThread(Person p){this.p=p;}public void run(){int count=1;while (true){p.set("zhangsan"+count++, count);}}}class GetThread extends Thread{private Person p;GetThread(Person p){this.p=p;}public void run(){while (true){p.get();}}}



d.停止线程,就是让其run()方法执行完毕,而通常run()方法中都涉及无限次的循环,所以要让run()方法执行完毕,就得让循环条件变为false,如下代码中,调用线程对象t1,t2的interrupt()虽然不能使线程对象停止,但是该方法会返回给线程对象一个InterruptedException类型的异常对象,而该异常对象刚好能够被run()方法中的catch代码块捕捉到,所以我们在catch代码块中添加代码flag=false便可以停止线程对象。

public class StopThread implements Runnable{private boolean flag=true;public synchronized void run(){while(flag)try {this.wait();} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName()+":Exception is running...");flag=false;}System.out.println(Thread.currentThread().getName()+":Thread is running...");}public void changeFlag(){flag=false;}public static void main(String[] args) {StopThread st=new StopThread();Thread t1=new Thread(st);Thread t2=new Thread(st);t1.start();t2.start();int num=0;while(true){if(num++==60){t1.interrupt();t2.interrupt();break;}System.out.println(Thread.currentThread().getName()+":"+num);}}}

e.我们还可以将一个线程对象在开启之前就设置为守护(后台)线程,通过t.setDaemon(true)方法进行设置,这样只要前台主线程一结束,守护线程自动就结束了。

f.线程优先级,默认优先级为Thread.NORM_PRIORITY=5,最高和最低优先级分别为Thread.MAX_PRIORITY=10,Thread.MIN_PRIORITY=1,优先级的设置是通过t.setPriority(MAX_PRIORITY)。




 





0 0
原创粉丝点击