以一個取錢列子來分析:(用戶登陸那些省略) 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()); } } }
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(); } }
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()); } } }
(synchronized能夠修飾方法,代碼塊。不能修飾屬性和構造方法) ide
除了同步代碼塊外還可使用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方法中,這種作法才更符合面向對象規則中的DDD(Domain Driven Design領域驅動設計) 性能
對於可變類的同步會下降程序運行效率。不要對線程安全類德全部方法進行同步,只對那些會改變共享資源的方法同步。
單線程環境(可使用線程不安全版本保證性能) 多線程環境(線程安全版本)
A.當前線程的同步方法,同步塊執行結束。當前線程釋放同步監視器
B.在同步方法,塊中遇到break,return終止了該代碼塊,方法.釋放
C.在代碼塊,方法中出現Error,Exception
D.執行同步時,程序執行了同步監視器對象的wait()方法,則當前線程暫停,釋放
A.執行同步時,程序調用Thread.sleep(),Thread.yield()方法來暫停當前線程的執行,不會釋放
B.執行同步時,其餘線程調用了該線程的suspend方法將該線程掛起,不會釋放(可是儘可能避免使用suspend和resume來控制線程,容易致使死鎖)
在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方法,獲取超時失效鎖的tryLock(long,TimeUnit)方法)。
ReentrantLock鎖具備重入性,即線程能夠對它已經加鎖的ReentrantLock鎖再次加鎖,ReentrantLock對象會維持一個計數器來追蹤lock方法的嵌套調用,線程每次調用lock()加鎖後,必須顯示的調用unlock()釋放鎖,因此一段被鎖保護的代碼能夠調用另外一個被相同鎖保護的方法。
當兩個線程相互等待對方釋放同步監視器的時候就會發生死鎖,一旦出現死鎖,整個程序既不會發生任何異常,也不會有任何提示,只是全部線程處於阻塞狀態,沒法繼續。
線程在系統內運行時,線程的調度具備必定的透明性,程序一般沒法準確控制線程的輪換執行,能夠經過如下方法來保證線程協調運行.
若是對於一些方法是用同步方法或者同步代碼塊,那麼能夠調用Object類提供的wait(),notify(),notifyAll()。這三個不屬於Thread,屬於Object類,但必須由同步監視器來調用(同步方法的監視器是this:則需this.wait()....,同步代碼塊的監視器是括號中的obj.wait());
若是程序沒有使用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次,因此阻塞。
阻塞和死鎖是不一致的,這裏阻塞只是在等待取錢。。。
上面的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條)
線程組能夠對一批線程進行分類管理,Java也容許程序直接對線程組進行控制。對線程組的控制至關於同時控制這批線程。用戶建立的全部線程都屬於指定線程組,若是沒有顯示指定線程屬於哪一個線程組,那麼這個線程屬於默認線程組。在默認狀況下,子線程和建立它的父線程處於同一個線程組內:例如A建立B線程,B沒有指定線程組,那麼A和B屬於同一個線程組。
一旦某個線程加入到指定線程組,那麼該線程就一直屬於該線程組,直到該線程死亡,線程運行中途不能改變它所屬的線程組(中途不能改變線程組,因此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); }
//不屬於任何一個線程組,是一個必須的用來建立系統線程的組 /** * 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); }
源碼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屬於私人線程組
不想在多線程中遇到無謂的Exception,從jdk1.5後,java增強了線程的異常處理,若是線程執行過程當中拋出了一個未處理的異常,JVM在結束該線程以前就會自動查找是否有對應的Thread.UncaughtExceptionHandler對象,若是找到該處理對象,將會調用該對象的uncaughtException(Thread t,Throwable 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