文章目錄
Java 多線程(上)1 線程和進程的區別1.1進程和線程
進程是指一個記憶體中啟動並執行應用程式,每個進程都有自己獨立的一塊記憶體空間,一個進程中可以有多個線程。比如在Windows系統中,一個啟動並執行xx.exe就是一個進程。Java程式的進程裡有幾個線程:主線程, 記憶體回收線程(後台線程)。
線程是指進程中的一個執行任務(控制單元),一個進程中可以運行多個線程,多個線程可共用資料。多進程:作業系統中同時啟動並執行多個程式,在同一個進程中同時啟動並執行多個任務;一個進程至少有一個線程,為了提高效率,可以在一個進程中開啟多個控制單元。
並發運行。如:多線程下載軟體。
多線程下載:此時線程可以理解為下載的通道,一個線程就是一個檔案的下載通道,多線程也就是同時開起好幾個下載通道.當伺服器提供下載服務時,使用下載者是共用頻寬的,在優先順序相同的情況下,總伺服器會對總下載線程進行平均分配。不難理解,如果你線程多的話,那下載的越快。現流行的下載軟體都支援多線程。可以完成同時運行,但是通過程式啟動並執行結果發現,雖然同時運行,但是每一次結果都不一致。
因為多線程存在一個特性:隨機性。
造成的原因:CPU在瞬間不斷切換去處理各個線程而導致的。
可以理解成多個線程在搶cpu資源。
1.2 線程與進程的比較
線程具有許多傳統進程所具有的特徵,故又稱為輕型進程或進程元;而把傳統的進程稱為重型進程,它相當於只有一個線程的任務。在引入了線程的作業系統中,通常一個進程都有若干個線程,至少需要一個線程。
進程與線程的區別:
1.進程有獨立的進程空間,進程中的資料存放空間(堆空間和棧空間)是獨立的。
2.線程的堆空間是共用的,棧空間是獨立的,線程消耗的資源也比進程小,相互之間可以影響的。
如果想詳細瞭解線程與進程請看我的《線程與進程的詳細分析》。
2. 建立線程 2.1 建立線程第一種方式(繼承):
1. 建立一個類,繼承Thread
2. 複寫 run方法
3. 建立一個線程對象
4. 啟動線程(線程對象.start())
代碼如下:
class MyThread extends Thread{public void run() {for (int i = 0; i < 100; i++) {System.out.println("MyThread---->"+ i);}}}class ThreadDemo1 {public static void main(String[] args) {for (int i = 0; i < 100; i++) {System.out.println("main------>" +i);if(i == 10){new MyThread().start();}} }}
2.2 建立線程第二種方式(實現):
實現Runnable介面
1. 子類覆蓋介面中的run方法。
2. 通過Thread類建立線程,並將實現了Runnable介面的子類對象作為參數傳遞給Thread類的建構函式。
3. Thread類對象調用start方法開啟線程。代碼如下:
class MyThread2 implements Runnable {public void run() {// 線程體for (int i = 0; i < 100; i++) {System.out.println("MyThread2----->" + i);}}}public class ThreadDemo2 {public static void main(String[] args) {for (int i = 0; i < 100; i++) {System.out.println("main-->" +i);if (i == 10) {new Thread(new MyThread2()).start();}}}}
現在我們通過一個經典案例來說明這兩種方式的區別:
售票的例子,這個例子基本上每一本java入門書上都會有!
需求:有50張票需要3個售票視窗賣出;用兩種開啟線程方式買票,觀察兩種方式買票的結果有什麼不同?
1. 繼承Thread方式
class Ticket1 extends Thread{int num = 20;public Ticket1(String name){super(name);}public void run() {for (int i = 0; i < 100; i++) {if(num >0) {System.out.println(getName()+"賣出第" +num-- +"張");}}}}public class TicketDemo {public static void main(String[] args) {//3個視窗買new Ticket1("售票員-1").start();new Ticket1("售票員-2").start();new Ticket1("售票員-3").start();} }
輸出結果的一部分:
售票員-1賣出第50張
售票員-3賣出第50張
售票員-2賣出第50張
售票員-3賣出第49張
售票員-1賣出第49張
售票員-3賣出第48張
售票員-3賣出第47張
2. 實現Runnable方式
class Ticket2 extends Object implements Runnable{int num = 20;public void run() {for (int i = 0; i < 50; i++) {if(num >0) {System.out.println(Thread.currentThread().getName()+"賣出第" +num-- +"張");
}}}}public class TicketDemo2 {public static void main(String[] args) {Runnable target = new Ticket2();new Thread(target,"售票員-1").start();new Thread(target,"售票員-2").start();new Thread(target,"售票員-3").start();}}
輸出結果的一部分:
售票員-1賣出第50張
售票員-3賣出第48張
售票員-2賣出第49張
售票員-3賣出第46張
售票員-2賣出第45張
售票員-2賣出第43張
currentThread():返回對當前正在執行的線程對象的引用。
getName():擷取線程名稱。
setName()設定線程名字。
將兩種方法的輸出結果進行比較,我們會發現
繼承Thread類的輸出結果一共列印了150條,說明一條票被賣了三次,這顯然是不正確的。
而實現Runnable介面卻沒有出現這樣的問題。
解釋:
因為一個線程只能啟動一次,通過Thread實現線程時,線程和線程所要執行的任務是捆綁在一起的。也就使得一個任務只能啟動一個線程,不同的線程執行的任務是不相同的,所以沒有必要,也不能讓兩個線程共用彼此任務中的資源。
一個任務可以啟動多個線程,通過Runnable方式實現的線程,實際是開闢一個線程,將任務傳遞進去,由此線程執行。可以執行個體化多個 Thread對象,將同一任務傳遞進去,也就是一個任務可以啟動多個線程來執行它。這些線程執行的是同一個任務,所以他們的資源是共用。
兩種不同的線程實現方式本身就決定了其是否能進行資源共用
繼承Thread
同份資源不共用並且由於java的單繼承,程式以後不便於擴充。
實現Runnable:(推薦)
多個線程共用一個目標資源,適合多執行緒同一份資源。
該類還可以繼承其他類,也可以實現其他介面。
3 線程的生命週期
3.1 線程的生命週期之建立和就緒狀態
建立:當程式使用new建立一個線程後,該線程處於建立狀態,此時他和其他java對象一樣,僅僅由Java虛擬機器為其分配記憶體並初始化成員變數值。【 Thread r = new Thread() 】
就緒:當線程對象調用start()方法後,該線程處於就緒狀態,線程計入線程隊列排隊,此時該狀態線程並未開始執行,它僅表示可以運行了。至於該線程何時運行,取決於JVM線程調度器的調度。【 r.start() 】
3.2 線程的生命週期之運行和阻塞狀態
運行:若處於就緒狀態的線程獲得了CPU,開始執行run()線程執行體,該線程處於執行狀態。
阻塞:線程運行過程中需要被中斷,目的是是其他的線程獲得執行的機會。該狀態就會進入阻塞狀態。
注意:阻塞狀態不能直接轉成運行狀態,阻塞狀態只能重新進入就緒狀態。
3.3 線程的生命週期之死亡
run()執行完成,線程正常結束; 線程拋出未捕獲的Exception或Error;
調用線程的stop()。(易導致死結,不推薦)
注意:
主線程結束後,其他線程不受其影響,不會隨之結束;
一旦子線程啟動起來後,就擁有和主線程相等地位,不受主線程影響。
測試線程是否活著,可用線程對象的isAlive()方法。當線程處於就緒,運行,阻塞狀態返回true。當線程處於建立和死亡狀態,返回false。
已死亡的線程是不可以通過start()方法喚醒線程的,否則引發IllegalThreadStateException異常;
樓豬不是什麼大牛,但自我評價覺得還總結的不錯,挺適合新手。 如果發現哪裡錯了,望各位大牛指點!