建立Java多執行緒的兩種方式和執行緒異常
一.使用多執行緒的兩種方法
使用多執行緒的兩種方法有:繼承Thread類和實現runable介面。
二.繼承Thread類
來看一下thread類的原始碼:
class Thread implements Runnable {
首先可以看出thread類也是實現Runable介面的run方法如下:
public void run() { if (target != null) { target.run(); } }
下面就是一個建立繼承Thread的類的列子:
public class ExThreadText extends Thread { @Override public void run(){ for(int i=0;i<20;i++){ System.out.println("我自己建立的執行緒"); } } public static void main(String[] args) { new ExThreadText().start(); System.out.println("程式結束!"); } }
結果如下
首先我們需要明白在這個程式裡面有多少個執行緒?應該是兩個執行緒一個是main方法的執行緒一個是我run方法裡面的一個執行緒
從結果可以看出這兩個執行緒的呼叫和建立的順序是無關的,
在這個程式碼例項裡面我們重寫了run方法,並使用 start 方法來呼叫,那為什麼不用run方法來呼叫呢?我們來看看run方法呼叫會有什麼結果:
可以看出現在的兩個"執行緒"已經是按照順序執行的了,其實現在並不是多執行緒,就是一個單執行緒按照流程來執行。
總結:1.多執行緒的呼叫是無序的
2.多執行緒需要使用start方法來呼叫而不是run方法,同樣start方法來呼叫執行緒也是無序的
三.使用runable介面來實現多執行緒
由於Java不提供多繼承,所以當我們繼承了Thread類的時候我們就不能繼承其它的父類了,為了解決這一個問題,我建議實現runable介面。
首先繼承runable介面必須重寫他的run方法,程式碼如下:
public class ImRunableText implements Runnable { @Override public void run(){ //重寫run方法 } }
那怎麼使用這個runable介面的子類呢?
我們來看看Thread類的構造方法:
可以看出Thread類的構造器可以接受實現Runable介面的子類物件(不要忘了thread類也是實現runable介面的),同時他還過載了一個構造器可以為執行緒命名。
那麼我們就可以使用這個runable的實現子類了,程式碼如下:
public class ImRunableText implements Runnable { @Override public void run(){ //重寫run方法 for(int i=0;i<10;i++){ System.out.println("run方法"); } } public static void main(String[] args) { Thread thread =new Thread(new ImRunableText(),"執行緒"); thread.start(); System.out.println("結束了"); } }
結果如下:
四.執行緒中的資料共享和執行緒安全
4.1資料不共享
資料不共享那就是一個執行緒一個數據,單獨執行互不影響。程式碼如下:
這是一個執行緒類,他有自己的欄位num為10。
public class DataNShare extends Thread{ private int num =10; private String name; public DataNShare(String name){ this.name=name; } @Override publicvoid run(){ for(;num>0;){ System.out.println("當前執行緒為:"+name); System.out.println("num值為"+num); num--; } } }
在設定一個測試類,建立三個物件,各自進行測試程式碼如下:
public class Text { public static void main(String[] args) { DataNShare d1=new DataNShare("執行緒1"); DataNShare d2=new DataNShare("執行緒2"); DataNShare d3=new DataNShare("執行緒3"); d1.start(); d2.start(); d3.start(); } }
測試結果:
可以看出一開始是沒有問題的,但是在後面出現了亂序的情況。
那麼出現了亂序的情況是不是就一定證明了程式出錯了呢?
我們來改進一下這個DataNShare類中的Run方法
publicvoid run(){ for(int i =1;num>=0;i++){ System.out.println("當前執行緒為:"+name); System.out.println("num值為"+num); num--; if(num==0){ System.out.println("*************i的值為"+i+"*************"); } } }
那麼也就是說當我的 i 值輸出每一次輸出10那麼就是代表每一條執行緒都是執行互不影響的。
*3,雖然還是存在亂序的情況,但是至少保證我們的每一條執行緒執行都是10次沒有問題的。那麼出現亂序的情況就是輸出語句的問題。
4.2資料共享
資料共享就是多個執行緒可以訪問一個數據,程式碼如下:
放有共享資料的執行緒類:
public class DataShare extends Thread { private int num=3;//共享資料 @Override public void run(){ num--;//共享資料減一 System.out.println("當前執行緒為:"+this.currentThread().getName()+"num值為:"+num); } }
處理類:
public class Text { public static void main(String[] args) { //將共享資料放入3個執行緒裡進行處理 DataShare d=new DataShare(); Thread t1=new Thread(d,"t1"); Thread t2=new Thread(d,"t2"); Thread t3=new Thread(d,"t3"); t1.start(); t2.start(); t3.start(); } }
結果如下:
在這裡出現的這種情況就是執行緒不安全狀態。那麼怎麼解決這個問題呢?
使用關鍵字synchronized(同步化的)來解決。
當一個方法使用該關鍵字那麼在多個執行緒執行這個方法時,每一個執行緒獲得執行該方法的執行權就會把這個方法上鎖結束後開鎖,只有等到這個方法沒有被上鎖時才可以被其他執行緒執行。
看看改進後的程式碼:
public class DataShare extends Thread { private int num=3;//共享資料 @Override synchronized public void run(){ num--;//共享資料減一 System.out.println("當前執行緒為:"+this.currentThread().getName()+"num值為:"+num); } }
結果:
總結:當多個執行緒在共享一個數據時,可能會造成執行緒異常,應該使用關鍵字synchronized來實現同步化,在後面還會深入瞭解同步化。