Java多線程(全)學習筆記(中)

資源下載地址:http://download.csdn.net/detail/cloudyxuq/3763101

四.多線程的同步

以一個取錢列子來分析:(用戶登錄那些省略)

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方法中,這種做法才更符合面向對象規則中的DDDDomainDrivenDesign領域驅動設計)

對於可變類的同步會降低程序運行效率。不要對線程安全類德所有方法進行同步,只對那些會改變共享資源的方法同步。

單線程環境(可以使用線程不安全版本保證性能)多線程環境(線程安全版本)

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()




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中會根據參數個數而變,沒這個參數的就直接nulllong類型就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()獲得,但不允許改變線程組的名字。

源碼setMaxPriority(intpri)

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對象,如果找到該處理對象,將會調用該對象的uncaughtExceptionThreadtThrowablee)方法來處理該異常。

自定義線程異常需要繼承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.staticsetDefaultUncaughtExceptionHandler(UncaughtExceptionHandlereh)

爲該線程類的所有線程實例設置默認的異常處理器

源碼:

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

2.setUncaughtExceptionHandler(UncaughtExceptionHandlereh)

爲指定線程實例設置異常處理器

源碼:

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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章