在多線程(一)中,我們簡要地介紹了線程基本知識,下面主要介紹創建線程的兩種方法,并分析線程的幾種基本狀態。
創建線程的兩種方法##
第一種 是將類聲明成Thread的子類的方式####
該子類必須重寫 Thread 類的 run 方法,接下來new這個對象的實例。然后調用start()方法創建并啟動線程。需要注意的是:程序是在調用start()方法之后,才開辟了一個子線程,并執行run方法。如果你直接調run方法(比如在main方法最下面寫上m1.run();),它就是在主線程中執行的,而且是要比開辟三個線程要快的,這就說明新線程創建的過程不會阻塞主線程的后續執行。
多線程最經典例子:售票系統。
public class Test {
public static void main(String[] args) {
MyTicketThread m1= new MyTicketThread("1 號售票窗口");
MyTicketThread m2= new MyTicketThread("2 號售票窗口");
MyTicketThread m3= new MyTicketThread("3 號售票窗口");
m1.start();
m2.start();
m3.start();
}
}
class MyTicketThread extends Thread {
private int ticket = 8; // 8張票
private String ticketWindowName; // 賣票窗口
public MyTicketThread(String ticketWindowName) {
this.ticketWindowName = ticketWindowName;
}
@Override
public void run(){
for(int i = 0;i < 100; i++){
if(this.ticket > 0){
System.out.println(this.ticketWindowName + "賣票---->" + (this.ticket--));
}
}
}
}
注意:同一個Thread不能重復調用start方法,會出現java.lang.IllegalThreadStateException異常。
可以看到三條線程正在并發執行(電腦配置較好的,效果不明顯,要多運行幾次)。可以看到每個窗口都賣了8張票,這符合賣票的邏輯么?接著讓我們看看實現Runnable接口的并發方式。
1 號售票窗口賣票---->8
1 號售票窗口賣票---->7
1 號售票窗口賣票---->6
1 號售票窗口賣票---->5
2 號售票窗口賣票---->8
2 號售票窗口賣票---->7
2 號售票窗口賣票---->6
2 號售票窗口賣票---->5
2 號售票窗口賣票---->4
2 號售票窗口賣票---->3
2 號售票窗口賣票---->2
2 號售票窗口賣票---->1
1 號售票窗口賣票---->4
1 號售票窗口賣票---->3
1 號售票窗口賣票---->2
1 號售票窗口賣票---->1
3 號售票窗口賣票---->8
3 號售票窗口賣票---->7
3 號售票窗口賣票---->6
3 號售票窗口賣票---->5
3 號售票窗口賣票---->4
3 號售票窗口賣票---->3
3 號售票窗口賣票---->2
3 號售票窗口賣票---->1
第二種 通過實現Runnable接口的方式####
重寫run()方法,然后調用new Thread(runnable)的方式創建一個線程,然后調用start()方法啟動線程。
public class TestRunnable {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t1 = new Thread( myThread, "1 號售票窗口");
Thread t2 = new Thread( myThread, "2 號售票窗口");
Thread t3 = new Thread( myThread, "3 號售票窗口");
t1.start();
t2.start();
t3.start();
}
}
class MyThread implements Runnable{
private int ticket = 8; // 還是8張票
private String ticketWindowName; // 售票窗口
@Override
public void run(){
for(int i = 0;i < 100; i++){
if(this.ticket>0){
System.out.println(Thread.currentThread().getName() + "賣票---->" + (this.ticket--));
}
}
}
}
注意:Runnable接口只能通過Thread的靜態方法Thread.currentThread()取得當前的Thread對象,再調用getName()方法,來取得當前線程的名字。
可以看到是三個售票窗口共同把八張票賣完了。
1 號售票窗口賣票---->8
1 號售票窗口賣票---->6
1 號售票窗口賣票---->5
1 號售票窗口賣票---->4
3 號售票窗口賣票---->3
3 號售票窗口賣票---->1
2 號售票窗口賣票---->7
1 號售票窗口賣票---->2
兩種方式的結果為什么會不同呢?####
第一種,MyTicketThread繼承Thread類的,我們是創建了三個MyTicketThread的實例,同時開辟了三個線程,讓三個售票窗口都去執行“賣8張票”的任務,他們是各自賣各自的票,最后都賣完了8張票。
第二種實現Runnable的, 我們是創建了一個MyThread的實例,相當于建了“賣8張票”的任務,下面開辟了三個線程,相當于三個售票窗口去執行這個任務。
其實這完全是兩個不同的方式來實現多線程,一個是多個線程分別完成自己的任務,一個是多個線程共同完成一個任務。
實現Runnable接口比繼承Thread類所具有的優勢:####
- 適合多個相同的程序代碼的線程去處理同一個資源。
- 增加程序的健壯性,代碼可以被多個線程共享,代碼和數據獨立。
這種情況下第二種是很好的選擇,但是目前他也是不安全的,我們這里才8張票而已,如果是成千上萬張票呢?下面的博客我們會繼續優化這個例子。
線程的不同狀態即線程的生命周期###
我們每次運行上面的例子,三個線程的執行順序都不一樣,這是為什么呢?這是因為執行start方法的調用后,并不是立即執行多線程代碼,而是使得該線程變為可運行狀態(Runnable),什么時候運行是由操作系統決定的。此時三個線程都在爭奪系統資源,誰先運行,還真不一定。實際上所有的多線程代碼執行順序都是不確定的,每次執行的結果都是隨機的。從這里我們引申出線程的不同的運行狀態,詳見下圖。
- 新建狀態(New):新創建了一個線程對象。
- 就緒狀態(Runnable):線程對象創建后,其他線程調用了該對象的start()方法。該狀態的線程位于可運行線程池中,變得可運行,等待獲取CPU的使用權。
- 運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。
- 阻塞狀態(Blocked):阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:
(一)、等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。(wait會釋放持有的鎖)
(二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中。
(三)、其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。(注意,sleep是不會釋放持有的鎖) - 死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命周期。
在后面更新中,我們會結合具體事例,詳細介紹阻塞狀態下各種方法的用法。
寫完嘍!ㄟ(▔,▔)ㄏㄟ(▔,▔)ㄏㄟ(▔,▔)ㄏ
知識重在總結和梳理,只有不斷地去學習并運用,才能化為自己的東西。當你能為別人講明白的時候,說明自己已經掌握了。
歡迎轉載,轉載請注明出處!
如果有錯誤的地方,或者有您的見解,還請不嗇賜教!
喜歡的話,麻煩點個贊!
Java中的多線程(三)關于多線程管理的相關函數說明
Java中的多線程(一)多線程基礎之進程、線程、并發、并行。
參考文章:http://www.cnblogs.com/GarfieldEr007/p/5746362.html