標籤:自己 服務 turn 後台運行 center span 關鍵字 sleep 佔用
多線程一、多線程 1、進程與線程
進程:一個電腦程式的運行執行個體,包含了需要執行的指令;有自己的獨立地址空間,包含程式內容和資料;不同進程的地址空間是互相隔離的;
進程擁有各種資源和狀態資訊,包括開啟的檔案、子進程和訊號處理。
線程:表示程式的執行流程,是CPU調度執行的基本單位;線程有自己的程式計數器、寄存器、堆棧和幀。同一進程中的線程共用相同的地址空間,
同時共用進程鎖擁有的記憶體和其他資源。
2、Java標準庫提供了進程和線程相關的API
進程主要包括表示進程的java.lang.Process類和建立進程的java.lang.ProcessBuilder類;
表示線程的是java.lang.Thread類,在虛擬機器啟動之後,通常只有Java類的main方法這個普通線程運行,運行時可以建立和啟動新的線程;
有一類守護線程(damon thread),守護線程在後台運行,提供者運行時所需的服務。當虛擬機器中啟動並執行所有線程都是守護線程時,虛擬機器終止運行。
3、線程的生命週期
線程是一個動態執行的過程,它也有一個從產生到死亡的過程。
-
- 建立狀態:
使用 new 關鍵字和 Thread 類或其子類建立一個線程對象後,該線程對象就處於建立狀態。它保持這個狀態直到程式start() 這個線程。
- 就緒狀態:
當線程對象調用了start()方法之後,該線程就進入就緒狀態。就緒狀態的線程處於就緒隊列中,要等待JVM裡線程調度器的調度。
- 運行狀態:
如果就緒狀態的線程擷取 CPU 資源,就可以執行 run(),此時線程便處於運行狀態。處於運行狀態的線程最為複雜,它可以變為阻塞狀態、就緒狀態和死亡狀態。
- 阻塞狀態:
如果一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所佔用資源之後,該線程就從運行狀態進入阻塞狀態。在睡眠時間已到或獲得裝置資源後可以重新進入就緒狀態。可以分為三種:
-
等待阻塞:運行狀態中的線程執行 wait() 方法,使線程進入到等待阻塞狀態。
-
同步阻塞:線程在擷取 synchronized 同步鎖失敗(因為同步鎖被其他線程佔用)。
-
其他阻塞:通過調用線程的 sleep() 或 join() 發出了 I/O 請求時,線程就會進入到阻塞狀態。當sleep() 狀態逾時,join() 等待線程終止或逾時,或者 I/O 處理完畢,線程重新轉入就緒狀態。
- 死亡狀態:
一個運行狀態的線程完成任務或者其他終止條件發生時,該線程就切換到終止狀態。
4、線程的優先順序
每一個 Java 線程都有一個優先順序,這樣有助於作業系統確定線程的調度順序。
Java 線程的優先順序是一個整數,其取值範圍是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
預設情況下,每一個線程都會分配一個優先順序 NORM_PRIORITY(5)。
具有較高優先順序的線程對程式更重要,並且應該在低優先順序的線程之前分配處理器資源。但是,線程優先順序不能保證線程執行的順序,而且非常依賴於平台。
5、線程的建立
Java 提供了三種建立線程的方法:
-
- 通過實現 Runnable 介面;
- 通過繼承 Thread 類本身;
- 通過 Callable 和 Future 建立線程。
1、通過實現 Runnable 介面來建立線程
(1)定義runnable介面的實作類別,並重寫該介面的run()方法,該run()方法的方法體同樣是該線程的線程執行體。
(2)建立 Runnable實作類別的執行個體,並依此執行個體作為Thread的target來建立Thread對象,該Thread對象才是真正的線程對象。
(3)調用線程對象的start()方法來啟動該線程。
樣本:
1 package com.thread; 2 3 public class RunnableThreadTest implements Runnable 4 { 5 6 private int i; 7 public void run() 8 { 9 for(i = 0;i <100;i++) 10 { 11 System.out.println(Thread.currentThread().getName()+" "+i); 12 } 13 } 14 public static void main(String[] args) 15 { 16 for(int i = 0;i < 100;i++) 17 { 18 System.out.println(Thread.currentThread().getName()+" "+i); 19 if(i==20) 20 { 21 RunnableThreadTest rtt = new RunnableThreadTest(); 22 new Thread(rtt,"新線程1").start(); 23 new Thread(rtt,"新線程2").start(); 24 } 25 } 26 27 } 28 29 } 2、繼承Thread類建立線程類
(1)定義Thread類的子類,並重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務。因此把run()方法稱為執行體。
(2)建立Thread子類的執行個體,即建立了線程對象。
(3)調用線程對象的start()方法來啟動該線程。
1 package com.thread; 2 3 public class FirstThreadTest extends Thread{ 4 int i = 0; 5 //重寫run方法,run方法的方法體就是現場執行體 6 public void run() 7 { 8 for(;i<100;i++){ 9 System.out.println(getName()+" "+i); 10 11 } 12 } 13 public static void main(String[] args) 14 { 15 for(int i = 0;i< 100;i++) 16 { 17 System.out.println(Thread.currentThread().getName()+" : "+i); 18 if(i==20) 19 { 20 new FirstThreadTest().start(); 21 new FirstThreadTest().start(); 22 } 23 } 24 } 25 26 }
上述代碼中Thread.currentThread()方法返回當前正在執行的線程對象。GetName()方法返回調用該方法的線程的名字。
3、通過Callable和Future建立線程
(1)建立Callable介面的實作類別,並實現call()方法,該call()方法將作為線程執行體,並且有傳回值。
(2)建立Callable實作類別的執行個體,使用FutureTask類來封裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的傳回值。
(3)使用FutureTask對象作為Thread對象的target建立並啟動新線程。
(4)調用FutureTask對象的get()方法來獲得子線程執行結束後的傳回值
樣本:
1 package com.thread; 2 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.FutureTask; 6 7 public class CallableThreadTest implements Callable<Integer> 8 { 9 10 public static void main(String[] args) 11 { 12 CallableThreadTest ctt = new CallableThreadTest(); 13 FutureTask<Integer> ft = new FutureTask<>(ctt); 14 for(int i = 0;i < 100;i++) 15 { 16 System.out.println(Thread.currentThread().getName()+" 的迴圈變數i的值"+i); 17 if(i==20) 18 { 19 new Thread(ft,"有傳回值的線程").start(); 20 } 21 } 22 try 23 { 24 System.out.println("子線程的傳回值:"+ft.get()); 25 } catch (InterruptedException e) 26 { 27 e.printStackTrace(); 28 } catch (ExecutionException e) 29 { 30 e.printStackTrace(); 31 } 32 33 } 34 35 @Override 36 public Integer call() throws Exception 37 { 38 int i = 0; 39 for(;i<100;i++) 40 { 41 System.out.println(Thread.currentThread().getName()+" "+i); 42 } 43 return i; 44 } 45 46 } 4、建立線程的三種方式的對比
採用實現Runnable、Callable介面的方式創見多線程時,
優勢是:
線程類只是實現了Runnable介面或Callable介面,還可以繼承其他類。
在這種方式下,多個線程可以共用同一個target對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU、代碼和資料分開,形成清晰的模型,較好地體現了物件導向的思想。
劣勢是:
編程稍微複雜,如果要訪問當前線程,則必須使用Thread.currentThread()方法。
使用繼承Thread類的方式建立多線程時
優勢是:
編寫簡單,如果需要訪問當前線程,則無需使用Thread.currentThread()方法,直接使用this即可獲得當前線程。
劣勢是:
線程類已經繼承了Thread類,所以不能再繼承其他父類。
5、小結:
在多線程的使用上:有效利用多線程的關鍵是理解程式是並發執行而不是串列執行的。例如:程式中有兩個子系統需要並發執行,這時候就需要利用多線程編程。
另外在多線程編程時:
重點理解:
-
-
-
- 線程同步
- 線程間通訊
- 線程死結
- 線程式控制制:掛起、停止和恢複
參考部落格:http://blog.csdn.net/escaflone/article/details/10418651
http://blog.csdn.net/longshengguoji/article/details/41126119
http://www.runoob.com/java/java-multithreading.html
http://www.tiantianbianma.com/java-wait-notify.html/
java進階學習--java多線程