Java多線程編程之使用多線程__演算法

來源:互聯網
上載者:User
繼承Thread類:

在Java中,內建了對多線程技術的支援。繼承Thread,還有實現Runnable介面。先看一下Thread類的結果,如下:

public class Thread implements Runnable

從上面的代碼中發現,Thread類實現了Runnable介面,它們之間具有多態關係。

這種方法的局限是不支援多繼承,因為Java是單繼承的,所以為了多繼承,完全可以實現Runnable介面的方法。

一邊實現一邊繼承,但是這兩種方法建立線程在工作時性質是一樣的,沒有本質區別。

public class MyThread extends Thread{@Overridepublic void run() {super.run();System.out.println("MyThread");}}public class Run {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start();System.out.println("運行結束!");}}

代碼的運用結果:

從圖結果看,MyThread類的run方法執行較晚,這也說明在使用多線程技術時,代碼的執行結果與代碼順序或代碼調用順序無關的。

如果多次調用start()方法,則會出現java.lang.IllegalThreadStateException異常

上面介紹了線程的調用隨機性,下面通過例子來示範線程的隨機性:

建立自訂線程類MyThread.java,代碼如下:

public class MyThread extends Thread{@Overridepublic void run() {super.run();for(int i=0;i<10;i++){int time = (int)(Math.random()*1000);try {Thread.sleep(time);System.out.println("run="+Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}}}

再建立運行類:

public class Run {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.setName("myThread");myThread.start();for(int i=0;i<10;i++){int time = (int)(Math.random()*1000);try {Thread.sleep(time);System.out.println("main="+Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}}}

在代碼中,為了展現出線程具有隨機性,所以使用隨機數的形式來使用線程得到掛起效果,從而表現CPU執行哪個線程具有不確定性。

Thread類的start()方法通知“線程規劃器”此線程已經準備就緒,等待調用線程對象的run()方法。這個過程其實就是讓系統安排一個時間來調用Thread中的run()方法,也就是使線程得到運行,啟動線程具有非同步執行的效果。如果調用代碼thread.run()就不是非同步執行,而是同步。那麼此線程對象並不交給“線程規劃器”來進行處理,而是由main主線程來調用run()方法,也就是必須等run()方法中的代碼執行完後才執行後面的代碼。

以非同步方式運行效果如下:

另外還需注意,執行start()方法的順序並不代表線程啟動的順序。

線程類代碼如下:

public class TheadT1 extends Thread{private int i;public TheadT1(int i){this.i = i;}public void run() {System.out.println(i);}}

執行類代碼:

public class TheadT1Test {public static void main(String[] args) {TheadT1 t1 = new TheadT1(1);TheadT1 t2 = new TheadT1(2);TheadT1 t3 = new TheadT1(3);TheadT1 t4 = new TheadT1(4);TheadT1 t5 = new TheadT1(5);t1.start();t2.start();t3.start();t4.start();t5.start();}}

程式運用結果如下圖:
實現Runnable介面:

如果欲建立的線程類已經有一個父類的話,那隻能通過實現Runnable介面實現多線程功能。

建立一個實現Runnable介面的類,代碼如下:

public class MyRunnable implements Runnable{@Overridepublic void run() {System.out.println("MyRunnable");}}public class RunnableRun {public static void main(String[] args) {MyRunnable run = new MyRunnable();Thread t = new Thread(run);t.start();System.out.println("運用結束!");}}

運用結果:

使用Thread類的方式來開發多線程應用程式在設計上是有局限性的,因為Java是單繼承,所以為了改變這種限制,可以使用Runnbale介面的方式來實現多線程技術。
執行個體變數與安全執行緒:

自訂線程類中的執行個體變數針對其他線程可以有共用與不共用之分,這在多個線程之間進行互動時是很重要的一個技術點。

(1)   不共用資料的情況:

通過下面一個樣本查看資料不共用情況。


範例程式碼如下:

public class SharedIsNoThread extends Thread{private int count = 5;public SharedIsNoThread(String name){this.setName(name);}@Overridepublic void run() {super.run();while(count>0){count--;System.out.println("由"+Thread.currentThread().getName()+"計算,count="+count);}}}public class SharedIsNoThreadTest {public static void main(String[] args) {SharedIsNoThread t1 = new SharedIsNoThread("A");SharedIsNoThread t2 = new SharedIsNoThread("B");SharedIsNoThread t3 = new SharedIsNoThread("C");t1.start();t2.start();t3.start();}}

運行結果如下圖:

通過程式表明,一共建立了3個線程,每個線程都有各自的count變數,自己減少自己的count變數的值。這樣的情況就是變數不共用,此樣本並不存在多個線程訪問同一個執行個體變數的情況。

(2)   共用資料的情況

共用資料的情況如下圖所示:

共用資料的情況就是多個線程可以訪問同一個變數,比如在實現投票功能時,多個線程可以同時處理同一個人的票數。

下面通過一個樣本看一下資料共用情況:

public class SharedThread extends Thread{private int count = 5;@Overridepublic void run() {super.run();while(count>0){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}count--;System.out.println("由"+Thread.currentThread().getName()+"計算,count="+count);}}}public class SharedThreadTest {public static void main(String[] args) {SharedThread sharedThread = new SharedThread();Thread t1 = new Thread(sharedThread, "A");Thread t2 = new Thread(sharedThread, "B");Thread t3 = new Thread(sharedThread, "C");t1.start();t2.start();t3.start();}}

休眠一秒,是為了情景更容易出現。

運行結果如下圖:

從輸出結果來看,出現了負數,產生了“非安全執行緒”問題。而我們想要的列印結果卻是不重複的,而是依次遞減並且最小等於0的。

在某些jvm中,i—的操作要分成如下3步:

(1)   取得原有i值。

(2)   計算i-1。

(3)   對i進行賦值。

而且在我們程式中還有while判斷這一步。如果出現多個線程訪問,那麼一定會出現非安全執行緒問題。

其實這個樣本就是典型的銷售情境:3個銷售人員,每個銷售人員賣出一個貨品後不可以得出相同的剩餘數量,必須在每個銷售人員賣完一個貨品後,其他銷售人員才可以在新的剩餘物品數上繼續減1操作。這時候就需要使多個線程間進行同步,也就是按順序的方式進行減1操作。更改代碼如下:

public class SharedThread extends Thread{private int count = 5;@Overridesynchronized public void run() {super.run();while(count>0){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}count--;System.out.println("有"+Thread.currentThread().getName()+"計算,count="+count);}}}

重新執行程式,就不會出現負數和值一樣的情況。

通過run方法前加入synchronized關鍵字,使多個線程執行run()方法時,以排隊的方式進行。當一個線程調用run方法前,先判斷run方法有沒有被上鎖,如上鎖,說明有其他線程正在調用run方法,必須等其他線程對run方法調用結束才可以調用run方法。這樣也就實現了排隊調用run方法的目的,也就達到了按順序對count變數減1的效果。synchronized關鍵字可以在任意對象及方法上加鎖,而加鎖的這段代碼稱為“互斥區”或“臨界區”。 當一個線程想要執行同步代碼塊中代碼時,線程首先嘗試擷取鎖,如果能拿到這把鎖,那麼這個線程就能執行同步代碼塊中的代碼。如果不能拿到這把鎖,那麼這個線程會不斷嘗試拿這把鎖,直到能夠拿到為止,而且是有多個線程同時爭搶這把鎖。流程如下圖:

留意i--與System.out.println()的異常:

上面介紹解決非安全執行緒問題使用的是synchronized關鍵字,這裡通過程式案例細化一下println()方法與i++聯合使用時“有可能”出現的另外一種異常情況,並說明其中的原因。 範例程式碼如下:

public class MyThread extends Thread{private int i = 5;@Overridepublic void run() {super.run();System.out.println("i="+(i--)+" threadName="+Thread.currentThread().getName());}}public class Run {public static void main(String[] args) {MyThread thread = new MyThread();Thread t1 = new Thread(thread);Thread t2 = new Thread(thread);Thread t3 = new Thread(thread);Thread t4 = new Thread(thread);Thread t5 = new Thread(thread);t1.start();t2.start();t3.start();t4.start();t5.start();}}

程式的執行結果:

本測試的用例是:雖然println()方法內部是同步的,但是i--的操作卻是在進入該方法前執行的,所以有發生非安全執行緒的問題的機率。


所以,為了防止發生非安全執行緒的問題,還是需要繼續使用同步方法。 currentThread()方法 currentThread() 方法可傳回碼段正在被哪個線程調用的資訊。下面通過一個樣本進行說明。具體如下:

public class Run {public static void main(String[] args) {System.out.print(Thread.currentThread().getName());}}

運行結果:

結果說明,main方法被名為main的線程調用。

繼續實驗,建立如下代碼:

public class MyThread extends Thread{public MyThread(){System.out.println("構造方法的列印:"+Thread.currentThread().getName());}@Overridepublic void run() {System.out.println("run方法的列印:"+Thread.currentThread().getName());}}public class Run {public static void main(String[] args) {Thread myThread = new MyThread();myThread.start();}}

執行結果如下:

通過結果可以看出,MyThread類的構造方法是被main線程調用的,而run方法是被名稱為Thread-0的線程調用的,run方法是自動調用的。

代碼改為如下:

public class Run {public static void main(String[] args) {Thread myThread = new MyThread();//myThread.start();myThread.run();}}


在來測試一個比較複雜的,範例程式碼如下:

public class CountOperate extends Thread{public CountOperate(){System.out.println("----CountOperate begin----");System.out.println("CountOperate Thread.currentThread().getName:"+Thread.currentThread().getName());System.out.println("CountOperate this.getName:"+this.getName());System.out.println("----CountOperate end----");}@Overridepublic void run() {System.out.println("----run begin----");System.out.println("run Thread.currentThread().getName:"+Thread.currentThread().getName());System.out.println("run this.getName:"+this.getName());System.out.println("----run end----");}}public class Run {public static void main(String[] args) {CountOperate c = new CountOperate();Thread t1 = new Thread(c,"A");System.out.println(t1);t1.start();}}
運行結果如下:


getName()和Thread.currentThread().getName()返回的值不同,你可以修改代碼如下:

public class CountOperate extends Thread{public CountOperate(String name){super(name);System.out.println("----CountOperate begin----");System.out.println("CountOperate Thread.currentThread().getName:"+Thread.currentThread().getName());System.out.println("CountOperate this.getName:"+this.getName());System.out.println("----CountOperate end----");}public class RunCountOperate {public static void main(String[] args) {CountOperate countOperate = new CountOperate("M");countOperate.setName("N");Thread t1 = new Thread(countOperate);t1.setName("A");t1.start();}}

執行結果如下:

通過這個結果可以看出:this.getName()方法是指當前對象的線程名稱,而Thread.currentThread.getName()方法表示正在啟動並執行線程的名稱。 isAlive()方法:

isAlive()方法的功能是判斷當前線程是否處於活動狀態。

建立範例程式碼,如下:

public class MyThread extends Thread{@Overridepublic void run() {System.out.println("---run:"+this.isAlive()+"---");}}public class Run {public static void main(String[] args) {MyThread m = new MyThread();System.out.println("begin="+m.isAlive());m.start();System.out.println("end="+m.isAlive());}}

程式運行結果如下:

方法isAlive()的作用是測試線程是否處於活動狀態。什麼是活動狀態。活動狀態就是線程已經啟動且尚未終止。線程處於正在運行或準備開始的狀態,就認為線程是存活的。

需要說明,如下代碼:

System.out.println("end="+m.isAlive());

雖然上面樣本列印的結果是true,但此值是不確定的。列印true是因為MyThread線程還沒有執行完畢,所以輸出true。如果代碼改為如下:

public class Run {public static void main(String[] args) throws InterruptedException {MyThread m = new MyThread();System.out.println("begin="+m.isAlive());m.start();//線程休息一秒Thread.sleep(1000);System.out.println("end="+m.isAlive());}}

執行結果:

上述代碼輸出為false,因為MyThread線程在1秒鐘內執行完畢了。

另外,在使用isAlive()方式時,如果將線程對象以構造參數的形式傳遞給Thread對象進行start()啟動時,啟動並執行結果和前面是有差異的。造成這樣的差異原因是Thread.currentThread()和this的區別。下面通過代碼測試一下:

public class CountOperate extends Thread{public CountOperate(String name){super(name);System.out.println("----CountOperate begin----");System.out.println("CountOperate Thread.currentThread().getName:"+Thread.currentThread().getName());System.out.println("CountOperate Thread.currentThread().isAlive:"+Thread.currentThread().isAlive());System.out.println("CountOperate this.getName:"+this.getName());System.out.println("CountOperate this.getName:"+this.isAlive());System.out.println("----CountOperate end----");}@Overridepublic void run() {System.out.println("----run begin----");System.out.println("run Thread.currentThread().getName:"+Thread.currentThread().getName());System.out.println("run Thread.currentThread().isAlive:"+Thread.currentThread().isAlive());System.out.println("run this.getName:"+this.getName());System.out.println("run this.isAlive:"+this.isAlive());System.out.println("----run end----");}}public class RunCountOperate {public static void main(String[] args) {CountOperate countOperate = new CountOperate("M");countOperate.setName("N");Thread t1 = new Thread(countOperate);System.out.println("main begin t1 isAlive="+t1.isAlive());t1.setName("A");t1.start();System.out.println("main end t1 isAlive="+t1.isAlive());}}

執行結果如下:

sleep()方法

方法sleep()的作用是在指定的毫秒數內讓當前“正在執行的線程”休眠(暫停執行)。這個“正在執行的線程”是指this.currentThread()返回的線程。

通過一個樣本說明,如下:

public class MyThread1 extends Thread{@Overridepublic void run() {System.out.println("run threadName="+this.currentThread().getName()+" begin");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("run threadName="+this.currentThread().getName()+" end");}}public class Run1 {public static void main(String[] args) {MyThread1 t1 = new MyThread1();System.out.println("begin="+System.currentTimeMillis());t1.run();System.out.println("end="+System.currentTimeMillis());}}

運行代碼結果如下:

繼續修改代碼,如下:

public class MyThread2 extends Thread{@Overridepublic void run() {System.out.println("run threadName="+this.currentThread().getName()+" begin="+System.currentTimeMillis());try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("run threadName="+this.currentThread().getName()+" end="+System.currentTimeMillis());}}public class Run2 {public static void main(String[] args) {MyThread2 t2 = new MyThread2();System.out.println("begin="+System.currentTimeMillis());t2.start();System.out.println("end="+System.currentTimeMillis());}}

運行結果如下:

由於main線程與Thread2線程是非同步執行的,所以首先列印的資訊begin和end。而MyThread2稍後執行,在最後兩行列印run begin 和 run end相關的資訊。 getId()方法

getId()方法的作用是取得線程的唯一標識。、

建立範例程式碼如下:

public class Run1 {public static void main(String[] args) {Thread thread = Thread.currentThread();System.out.println(thread.getName()+" "+thread.getId());}}

程式運行結果如下:



聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.