Java多线程(全)学习笔记(中)

四.多线程的同步

以一个取钱列子来分析:(用户登录那些省略)

Accout类:

/**银行取钱,账户类*/  
public class Accout {  
//账户编号  
private String accoutNo;  
//账户余额  
private double balance;  
//账户名称  
private String accoutName;  
public Accout(){  
super();  
}  
public Accout(String accoutNo,String accoutName, double balance) {  
super();  
this.accoutNo = accoutNo;  
this.balance = balance;  
this.accoutName=accoutName;  
}  
public String getAccoutNo() {  
return accoutNo;  
}  
public void setAccoutNo(String accoutNo) {  
this.accoutNo = accoutNo;  
}  
public double getBalance() {  
return balance;  
}  
public void setBalance(double balance) {  
this.balance = balance;  
}  
public String getAccoutName() {  
return accoutName;  
}  
public void setAccoutName(String accoutName) {  
this.accoutName = accoutName;  
}  
//根据accoutNohe来计算Accout的hashcode和判断equals  
@Override  
public int hashCode() {  
return accoutNo.hashCode();  
}  
@Override  
public boolean equals(Object obj) {  
if(obj!=null&&obj.getClass()==Accout.class){  
Accout target=(Accout)obj;  
return target.getAccoutNo().equals(accoutNo);  
}  
return false;  
}  
}




DrawThread类:

/**取钱的线程类*/  
public class DrawThread implements Runnable{  
//模拟用户账户  
private Accout accout;  
//当前取钱线程所希望取得值  
private double drawAmount;  
public DrawThread(Accout accout, double drawAmount) {  
super();  
this.accout = accout;  
this.drawAmount = drawAmount;  
}  
//如果多个线程修改同一个共享数据时,会发生数据安全问题  
public void run() {  
//账户余额大于取款金额时  
if(accout.getBalance()>=drawAmount){  
//取款成功  
System.out.println(Thread.currentThread().getName()+accout.getAccoutName()+"取款成功:吐出钞票:"+drawAmount);  
//修改余额  
accout.setBalance(accout.getBalance()-drawAmount);  
System.out.println("当前余额为:"+accout.getBalance());  
}  
//账户金额不够时  
else{  
System.out.println("账户金额不够,您的余额只有"+accout.getBalance());  
}  
}  
}



TestDraw测试类:
public class TestDraw {  
public static void main(String[]args) throws InterruptedException{  
//创建一个用户  
Accout acct=new Accout("123456", "小明", 1000);  
//模拟四个线程同时操作  
DrawThread dt=new DrawThread(acct,600);  
//DrawThread dt1=new DrawThread(acct,800);  
Thread th1=new Thread(dt,"线程1");  
Thread th2=new Thread(dt,"线程2");  
Thread th3=new Thread(dt,"线程3");  
Thread th4=new Thread(dt,"线程4");  
th4.join();  
th1.start();  
th2.start();  
th3.start();  
th4.start();  
}  
}



1.同步代码块

Java多线程支持引入了同步监视器来解决多线程安全,同步监视器的常用方法就是同步代码块:

Synchronized(obj){  
//...同步代码块  
}



括号中的obj就是同步监视器:上面的语句表示:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。这就意味着任何时刻只能有一条线程可以获得对同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定。

虽然java中对同步监视器使用的对象没有任何要求,但根据同步监视器的目的:阻止两条线程对同一个共享资源进行并发访问。所以一般将可能被并发访问的共享资源充当同步监视器。

修改后如下:

public void run() {  
synchronized (accout) {  
//账户余额大于取款金额时  
if(accout.getBalance()>=drawAmount){  
//取款成功  
System.out.println(Thread.currentThread().getName()+accout.getAccoutName()+"取款成功:吐出钞票:"+drawAmount);  
//修改余额  
accout.setBalance(accout.getBalance()-drawAmount);  
System.out.println("当前余额为:"+accout.getBalance());  
}  
//账户金额不够时  
else{  
System.out.println("账户金额不够,您的余额只有"+accout.getBalance());  
}  
}  
}



2.同步方法

(synchronized可以修饰方法,代码块。不能修饰属性和构造方法)

除了同步代码块外还可以使用synchronized关键字来修饰方法,那么这个修饰过的方法称为同步方法。对于同步方法来说,无需显式指定同步监视器,同步方法的同步监视器是this,也就是该对象本身,也就是上面TestDraw中定义的Accout类型的acct。

public void run() {  
draw();  
}  
public synchronized void draw(){  
if(accout.getBalance()>=drawAmount){  
//取款成功  
System.out.println(Thread.currentThread().getName()+accout.getAccoutName()+"取款成功:吐出钞票:"+drawAmount);  
//修改余额  
accout.setBalance(accout.getBalance()-drawAmount);  
System.out.println("当前余额为:"+accout.getBalance());  
}  
//账户金额不够时  
else{  
System.out.println("账户金额不够,您的余额只有"+accout.getBalance());  
}  
}



这里最好是将draw()方法写到Accout中,而不是像之前将取钱内容保存在run方法中,这种做法才更符合面向对象规则中的DDDDomain Driven Design领域驱动设计)

对于可变类的同步会降低程序运行效率。不要对线程安全类德所有方法进行同步,只对那些会改变共享资源的方法同步。

单线程环境(可以使用线程不安全版本保证性能) 多线程环境(线程安全版本)

2.1同步监视器的锁定什么时候释放

  A.当前线程的同步方法,同步块执行结束。当前线程释放同步监视器

  B.在同步方法,块中遇到break,return终止了该代码块,方法.释放

  C.在代码块,方法中出现Error,Exception

   D.执行同步时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,释放

2.2同步监视器的锁定在以下情况不会被释放

A.执行同步时,程序调用Thread.sleep(),Thread.yield()方法来暂停当前线程的执行,不会释放

B.执行同步时,其他线程调用了该线程的suspend方法将该线程挂起,不会释放(但是尽量避免使用suspend和resume来控制线程,容易导致死锁)

3.同步锁(Lock)

  在jdk1.5后,java除了上面两种同步代码块和同步方法之外,还提供了一种线程同步机制:它通过显示定义同步锁对象来实现同步,在这种机制下,同步锁应该使用Lock对象充当。

Lock是控制多个线程对共享资源进行访问的工具,每次只能有一个线程对Lock对象枷锁,线程开始访问共享资源之前应先获得Lock对象。(特例:ReadWriteLock锁可能允许对共享资源并发访问)。在实现线程安全控制中,通常喜欢使用可重用锁(ReentrantLock),使用该Lock对象可以显示的加锁,释放锁。

CODE:

//声明锁对象  
private final ReentrantLock relock=new ReentrantLock();  
public void run(){  
   draw();  
}  
public void draw() {  
       //加锁  
relock.lock();  
try{  
//账户余额大于取款金额时  
if(accout.getBalance()>=drawAmount){  
//取款成功  
System.out.println(Thread.currentThread().getName()+accout.getAccoutName()+"取款成功:吐出钞票:"+drawAmount);  
//修改余额  
accout.setBalance(accout.getBalance()-drawAmount);  
System.out.println("当前余额为:"+accout.getBalance());  
}  
//账户金额不够时  
else{  
System.out.println("账户金额不够,您的余额只有"+accout.getBalance());  
}  
}  
//释放锁  
finally{  
relock.unlock();  
}}



总结:

       同步方法和同步代码块使用与共享资源相关的,隐式的同步监视器,并且强制要求加锁和释放锁要出现在一个块结构中,而且当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有锁。

虽然同步方法,代码块的范围机制使多线程安全编程非常方便,还可以避免很多涉及锁的常见编程错误,但有时也需要以更灵活的方式使用锁。Lock提供了同步方法,代码块中没有的其他功能(用于非块结构的tryLock方法,获取可中断锁lockInterruptibly方法,获取超时失效锁的tryLocklongTimeUnit)方法)。

ReentrantLock锁具有重入性,即线程可以对它已经加锁的ReentrantLock锁再次加锁,ReentrantLock对象会维持一个计数器来追踪lock方法的嵌套调用,线程每次调用lock()加锁后,必须显示的调用unlock()释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。

4.死锁

当两个线程相互等待对方释放同步监视器的时候就会发生死锁,一旦出现死锁,整个程序既不会发生任何异常,也不会有任何提示,只是所有线程处于阻塞状态,无法继续。

五.线程通信

线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,可以通过以下方法来保证线程协调运行.

1.线程协调运行

如果对于一些方法是用同步方法或者同步代码块,那么可以调用Object类提供的wait(),notify(),notifyAll()。这三个不属于Thread,属于Object类,但必须由同步监视器来调用(同步方法的监视器是this:则需this.wait()....,同步代码块的监视器是括号中的obj.wait());

2.使用条件变量来控制协调

如果程序没有使用sychronized来保证同步,可以使用Lock来保证同步,则系统中就不存在隐式的同步监视器对象,也就不能使用wait,notify,notifyAll来协调了。

private final Lock lock=new ReentrantLock();  
private final Condition cond=lock.newCondition();



通过上面两行代码,条件Condition实际是绑定在一个Lock对象上的。

相对应的Condition类也有三个方法:

 await(),signal(),signalAll()

Java多线程(全)学习笔记(中)

Account账号类:

代码:

/** 账户类,用面向对象的DDD设计模式来设计 */  
/* 
 * DDD领域驱动模式,即将每个类都认为是一个完备的领域对象, 例如Account代表账户类,那么就应该提供用户账户的相关方法(存,取,转),而不是将 
 * setXXX方法暴露出来任人操作。 只要设计到DDD就需要重写equals和hashcode来判断对象的一致性 
 */  
public class Account {  
// 账户编码  
private String accountNo;  
// 账户余额  
private double balance;  
// 标示账户是否已有存款(此项目为了测试存入款就需要马上取出)  
private boolean flag = false;  
//  private final Lock lock=new ReentrantLock();  
//  private final Condition cond=lock.newCondition();  
public Account() {  
super();  
}  
public Account(String accountNo, double balance) {  
super();  
this.accountNo = accountNo;  
this.balance = balance;  
}  
// 取款(利用同步方法)  
public synchronized void draw(double drawAmount) {  
// 如果flag为假,没人存款进去,取钱方法(利用wait)阻塞(wait阻塞时,当前线程会释放同步监视器)  
try {  
if (!flag) {  
this.wait();//条件 cond.await();  
}  
//否则执行取钱  
else  
{  // System.out.println("账户余额:"+balance);  
System.out.println(Thread.currentThread().getName()+"---->取钱:"+drawAmount);  
balance-=drawAmount;  
System.out.println("账户余额: "+balance);  
   //设置flag(限定一个操作只能取一次钱)  
flag=false;  
//唤醒其他wait()线程  
this.notifyAll();//cond.signalAll();  
}  
} catch (InterruptedException e) {  
e.printStackTrace();  
}  
}  
//存款  
public synchronized void deposit(double depositAmount){  
//如果flag为真,证明有人存钱了,存钱阻塞  
try {  
if (flag) {  
this.wait(); //cond.await();  
}  
//否则执行存款  
else  
{  // System.out.println("账户余额:"+balance);  
System.out.println(Thread.currentThread().getName()+"---->存钱:"+depositAmount);  
balance+=depositAmount;  
System.out.println("账户余额: "+balance);  
   //设置flag(限定一个操作只能取一次钱)  
flag=true;  
//唤醒其他wait()线程  
this.notifyAll(); //cond.signalAll();  
}  
} catch (InterruptedException e) {  
e.printStackTrace();  
}  
}  
// DDD设计模式重写equals和hashcode(判断用户是否一致,只需要判断他们的账号编码就可以了,不需要再判断整个对象,提高性能)  
@Override  
public int hashCode() {  
return accountNo.hashCode();  
}  
@Override  
public boolean equals(Object obj) {  
if (obj != null && obj.getClass() == Account.class) {  
Account account = (Account) obj;  
return account.getAccountNo().equals(accountNo);  
}  
return false;  
}  
public String getAccountNo() {  
return accountNo;  
}  
public void setAccountNo(String accountNo) {  
this.accountNo = accountNo;  
}



取钱线程:
public class DrawThread implements Runnable {  
/* 
 * 模拟用户 
 */  
private Account account;  
//用户取钱数  
private double drawAmount;  
public DrawThread(Account account, double drawAmount) {  
super();  
this.account = account;  
this.drawAmount = drawAmount;  
}  
@Override  
public void run() {  
//重复10次取钱操作  
for(int i=0;i<10;i++){  
account.draw(drawAmount);  
}  
}  
}



存钱线程:
public class DepositThread  implements Runnable{  
/* 
 * 模拟用户 
 */  
private Account account;  
//用户存钱数  
private double depositAmount;  
public DepositThread(Account account, double depositAmount) {  
super();  
this.account = account;  
this.depositAmount = depositAmount;  
}  
@Override  
public void run() {  
//重复10次存钱操作  
for(int i=0;i<10;i++){  
account.deposit(depositAmount);  
}  
}  
}



测试类:
public class Test {  
public static void main(String []args){  
//创建一个用户没余额,等待先存款后取钱  
Account acct=new Account("123张",0);  
//取款800  
new Thread(new DrawThread(acct,800),"取款者").start();  
//存款2个人  
new Thread(new DepositThread(acct,800),"存款者甲").start();  
new Thread(new DepositThread(acct,800),"存款者乙").start();  
new Thread(new DepositThread(acct,800),"存款者丙").start();  
}  
}  
结果:  
存款者甲---->存钱:800.0  
账户余额: 800.0  
取款者---->取钱:800.0  
账户余额: 0.0  
存款者丙---->存钱:800.0  
账户余额: 800.0  
取款者---->取钱:800.0  
账户余额: 0.0  
存款者甲---->存钱:800.0  
账户余额: 800.0  
取款者---->取钱:800.0  
账户余额: 0.0  
存款者丙---->存钱:800.0  
账户余额: 800.0  
取款者---->取钱:800.0  
账户余额: 0.0  
存款者甲---->存钱:800.0  
账户余额: 800.0  
取款者---->取钱:800.0  
账户余额: 0.0  
存款者丙---->存钱:800.0  
账户余额: 800.0



但根据上面情况来看,显示用户被阻塞无法继续向下执行,这是因为存钱有三个线程 共有3*10=30次操作,而取钱只有10次,所以阻塞。

阻塞和死锁是不一致的,这里阻塞只是在等待取钱。。。

3.使用管道流(通信)

上面的1,2两种线程操作,与其称为线程间的通信,不如称为线程之间协调运行的控制策略还要恰当些。如果需要在两条线程之间惊醒更多的信息交互,则可以考虑使用管道流进行通信。

管道流有3中形式:

           1.字节流:PipedInputStream,PipedOutputStream

           2.字符流:PipedReader,PipedWriter

           3.新IO的管理Channel:Pipe.SinkChannel,Pipe.SourceChannel

使用管道的步骤:

           1.new创建管道输入输出流

           2.使用管道输入或输出流的connect方法连接这两个输入输出流

           3.将两个管道流分别传入两个线程

           4.两个线程分别依赖各自的流来进行通信

但是因为两个线程属于同一个进程,它们可以非常方便的共享数据,利用共享这个方式才应该是线程之间进行信息交流的最好方式,而不是使用管道流。如果是操作诸如聊天室那样的话,用管道通信效果会好些,共享资源的话,性能太低,出错概率高。

CODE:

/**读取管道中的信息线程*/  
public class ReaderThread implements Runnable {  
private PipedReader pr;  
private BufferedReader br;  
public ReaderThread() {  
super();  
}  
public ReaderThread(PipedReader pr) {  
super();  
this.pr = pr;  
//包装管道流  
this.br=new BufferedReader(pr);  
}  
public void run(){  
String buffer=null;  
System.out.println(Thread.currentThread().getName());  
try{  
//开始逐行读取管道流数据(假定管道流数据时字符流)  
System.out.println("------打印管道中的数据-------");  
while((buffer=br.readLine())!=null){  
System.out.println(buffer);  
}  
}  
catch(IOException e){  
e.printStackTrace();  
}  
finally{  
try {  
if(br!=null)  
br.close();  
} catch (IOException e) {  
e.printStackTrace();  
}  
}  
}  
}



/**像管道流中写入数据*/  
public class WriterThread implements Runnable {  
//定义一个数组来充当向管道中输入数据  
String []str=new String[]{"1.www.csdn.net论坛","2.www.google.com谷歌","3.www.hibernate.orgHibernate","4.www.xiami.com虾米"};  
private PipedWriter pw;  
public WriterThread() {  
}  
public WriterThread(PipedWriter pw) {  
this.pw = pw;  
}  
public void run(){  
System.out.println(Thread.currentThread().getName());  
try{  
//向管道流中写入数据,以供读取  
for(int i=0;i<100;i++){  
pw.write(str[i%4]+"\n");  
}  
}  
catch(IOException e){  
e.printStackTrace();  
}  
finally{  
try {  
if(pw!=null){  
pw.close();  
}   
}catch (IOException e) {  
// TODO Auto-generated catch block  
e.printStackTrace();  
}  
}  
}  
}



public class TestPiped {  
public static void main(String[] args) {  
PipedReader pr = null;  
PipedWriter pw = null;  
try {  
pw = new PipedWriter();  
pr = new PipedReader();  
// 链接管道  
pr.connect(pw);  
new Thread(new ReaderThread(pr),"读取管道线程").start();  
new Thread(new WriterThread(pw),"写入管道线程").start();  
} catch (IOException e) {  
e.printStackTrace();  
}  
}  
}  
结果:  
读取管道线程  
------打印管道中的数据-------  
写入管道线程  
1.www.csdn.net论坛  
2.www.google.com谷歌  
3.www.hibernate.orgHibernate  
4.www.xiami.com虾米  
1.www.csdn.net论坛  
2.www.google.com谷歌  
3.www.hibernate.orgHibernate  
4.www.xiami.com虾米  
1.www.csdn.net论坛  
.....(一共100条)



.线程组(ThreadGroup

1.线程组介绍:   

 线程组可以对一批线程进行分类管理,Java也允许程序直接对线程组进行控制。对线程组的控制相当于同时控制这批线程。用户创建的所有线程都属于指定线程组,如果没有显示指定线程属于哪个线程组,那么这个线程属于默认线程组。在默认情况下,子线程和创建它的父线程处于同一个线程组内:例如A创建B线程,B没有指定线程组,那么AB属于同一个线程组。

     一旦某个线程加入到指定线程组,那么该线程就一直属于该线程组,直到该线程死亡,线程运行中途不能改变它所属的线程组(中途不能改变线程组,所以Thread类只有getThreadGroup()方法来获得线程组,而没有set方法。)。

public final ThreadGroup getThreadGroup() {  
        return group;  
    }



Thread类中构造方法:

其中参数的个数根据情况而定,init中会根据参数个数而变,没这个参数的就直接null long类型就0.

public Thread(ThreadGroup group, Runnable target, String name,  
                long stackSize) {  
      init(group, target, name, stackSize);  
  }



ThreadGroup类中的构造方法:
//不属于任何一个线程组,是一个必须的用来创建系统线程的组  
  /** 
     * Creates an empty Thread group that is not in any Thread group. 
     * This method is used to create the system Thread group. 
     */  
    private ThreadGroup() {     // called from C code  
        this.name = "system";  
        this.maxPriority = Thread.MAX_PRIORITY;  
        this.parent = null;  
    }  
------------------------------------------------------  
 private ThreadGroup(Void unused, ThreadGroup parent, String name) {  
        this.name = name;  
        this.maxPriority = parent.maxPriority;  
        this.daemon = parent.daemon;  
        this.vmAllowSuspension = parent.vmAllowSuspension;  
        this.parent = parent;  
        parent.add(this);  
    }  
//指定线程组名创建一个线程组  
  public ThreadGroup(String name) {  
        this(Thread.currentThread().getThreadGroup(), name);  
    }  
//指定的父线程和线程组名来创建一个线程组  
   public ThreadGroup(ThreadGroup parent, String name) {  
        this(checkParentAccess(parent), parent, name);  
    }



  看出上面两个public构造器都需要指定一个名字给线程组,所以线程组总是具有一个字符串名字,该名称可调用ThreadGroup的getName()获得,但不允许改变线程组的名字。


Java多线程(全)学习笔记(中)

源码setMaxPriority(int pri)

public final void setMaxPriority(int pri) {  
        int ngroupsSnapshot;  
        ThreadGroup[] groupsSnapshot;  
        synchronized (this) {  
            checkAccess();  
            if (pri < Thread.MIN_PRIORITY || pri > Thread.MAX_PRIORITY) {  
                return;  
            }  
          maxPriority = (parent != null) ? Math.min(pri, parent.maxPriority) : pri;  
            ngroupsSnapshot = ngroups;  
            if (groups != null) {  
                groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);  
            } else {  
                groupsSnapshot = null;  
            }  
        }  
        for (int i = 0 ; i < ngroupsSnapshot ; i++) {  
            groupsSnapshot[i].setMaxPriority(pri);  
        }  
}



例子:

/**测试线程组*/  
public class TestThread implements Runnable {  
//指定线程组  
@Override  
public void run() {  
for(int i=0;i<10;i++){  
System.out.println(Thread.currentThread().getName()+"线程"+i+"属于"+Thread.currentThread().getThreadGroup().getName()+"线程组");  
}  
}  
}  
public class ThreadGroupTest {  
public static void main(String [] args){  
//获取主线程的线程组  
ThreadGroup mainGroup=Thread.currentThread().getThreadGroup();  
System.out.println("主线程的组的名字:"+mainGroup.getName());  
System.out.println("主线程组是否属于后台线程组:"+mainGroup.isDaemon());  
//新建一个线程组名字为“新线程组”,不设置父线程组  
ThreadGroup tg=new ThreadGroup("私人");  
tg.setDaemon(true);  
System.out.println(tg.getName()+"是否是后台线程组:"+tg.isDaemon());  
Thread th=new Thread(tg,new TestThread(),"线程1");  
System.out.println("1活动的线程有"+tg.activeCount());  
th.start();  
Thread th1=new Thread(tg,new TestThread(),"线程2");  
th1.start();  
System.out.println("2活动的线程有"+tg.activeCount());  
//Thread t1=new Thread();  
}  
}  
结果:  
主线程的组的名字:main  
主线程组是否属于后台线程组:false  
私人是否是后台线程组:true  
1活动的线程有0  
2活动的线程有2  
线程1线程0属于私人线程组  
线程1线程1属于私人线程组  
线程2线程0属于私人线程组  
线程1线程2属于私人线程组  
线程2线程1属于私人线程组  
线程1线程3属于私人线程组  
线程2线程2属于私人线程组  
线程1线程4属于私人线程组  
线程2线程3属于私人线程组  
线程1线程5属于私人线程组  
线程2线程4属于私人线程组  
线程1线程6属于私人线程组  
线程2线程5属于私人线程组  
线程1线程7属于私人线程组  
线程2线程6属于私人线程组  
线程2线程7属于私人线程组  
线程2线程8属于私人线程组  
线程2线程9属于私人线程组  
线程1线程8属于私人线程组  
线程1线程9属于私人线程组



2.线程异常处理

不想在多线程中遇到无谓的Exception,从jdk1.5后,java加强了线程的异常处理,如果线程执行过程中抛出了一个未处理的异常,JVM在结束该线程之前就会自动查找是否有对应的Thread.UncaughtExceptionHandler对象,如果找到该处理对象,将会调用该对象的uncaughtExceptionThread tThrowable e)方法来处理该异常。

自定义线程异常需要继承Thread.UncaughtExceptionHandler

Thread源码:

public interface UncaughtExceptionHandler {  
       /** 
        * Method invoked when the given thread terminates due to the 
        * given uncaught exception. 
        * <p>Any exception thrown by this method will be ignored by the 
        * Java Virtual Machine. 
        * @param t the thread 
        * @param e the exception 
        */  
       void uncaughtException(Thread t, Throwable e);  
   }



Thread类中提供两个方法来设置异常处理器:

1.static  setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)

为该线程类的所有线程实例设置默认的异常处理器

源码:

 

Public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {  
        SecurityManager sm = System.getSecurityManager();  
        if (sm != null) {  
            sm.checkPermission(  
                new RuntimePermission("setDefaultUncaughtExceptionHandler")  
                    );  
        }  
         defaultUncaughtExceptionHandler = eh;  
     }



2.setUncaughtExceptionHandler(UncaughtExceptionHandler eh)

为指定线程实例设置异常处理器

源码:

public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {  
        checkAccess();  
        uncaughtExceptionHandler = eh;  
}



其实ThreadGroup类继承了Thread.UncaughtExceptionHandler接口,所以每个线程所属的线程组将会作为默认的异常处理器。

所以可以认为当一个线程出现异常时,JVM首先会调用该线程的异常处理器(setUncaughtExceptionHandler),如果找到就执行该异常。没找到就会调用该线程所属线程组的异常处理器。

ThreadGroup类中异常处理器

源码:

public void uncaughtException(Thread t, Throwable e) {  
        if (parent != null) {  
            parent.uncaughtException(t, e);  
        } else {  
            Thread.UncaughtExceptionHandler ueh =  
                Thread.getDefaultUncaughtExceptionHandler();  
            if (ueh != null) {  
                ueh.uncaughtException(t, e);  
            } else if (!(e instanceof ThreadDeath)) {  
                System.err.print("Exception in thread \""  
                                 + t.getName() + "\" ");  
                e.printStackTrace(System.err);  
            }  
        }  
    }



例子:

/** 
 * 定义自己的异常类 
 */  
class MyEx implements Thread.UncaughtExceptionHandler{  
@Override  
public void uncaughtException(Thread t, Throwable e) {  
System.out.println(t+"线程出现了异常:"+e);  
}  
}  
/**为主线程设置异常处理器,当程序开始运行时抛出未处理的异常,自定义的异常处理器会起作用*/  
class MyThread extends Thread{  
public void run(){  
int a=5/0;  
}  
}  
public class ExHandler {  
public static void main(String []args){  
Thread td=new MyThread();  
//为指定的td线程实例设置异常处理器  
    td.setUncaughtExceptionHandler(new MyEx());  
td.start();  
//设置主线程的异常类  
Thread.currentThread().setUncaughtExceptionHandler(new MyEx());  
byte [] b=new byte[2];  
System.out.println(b[2]);  
}  
}  
结果:  
Thread[main,5,main]线程出现了异常:java.lang.ArrayIndexOutOfBoundsException: 2  
Thread[Thread-0,5,main]线程出现了异常:java.lang.ArithmeticException: / by zero



你可能感兴趣的:(Java多线程(全)学习笔记(中))