1. 程式人生 > >認識多工、多程序、單執行緒、多執行緒

認識多工、多程序、單執行緒、多執行緒

要認識多執行緒就要從作業系統的原理說起。 

  以前古老的DOS作業系統(V 6.22)是單任務的,還沒有執行緒的概念,系統在每次只能做一件事情。比如你在copy東西的時候不能rename檔名。為了提高系統的利用效率,採用批處理來批量執行任務。 

  現在的作業系統都是多工作業系統,每個執行的任務就是作業系統所做的一件事情,比如你在聽歌的同時還在用MSN和好友聊天。聽歌和聊天就是兩個任務,這個兩個任務是“同時”進行的。一個任務一般對應一個程序,也可能包含好幾個程序。比如執行的MSN就對應一個MSN的程序,如果你用的是windows系統,你就可以在工作管理員中看到作業系統正在執行的程序資訊。 

  一般來說,當執行一個應用程式的時候,就啟動了一個程序,當然有些會啟動多個程序。啟動程序的時候,作業系統會為程序分配資源,其中最主要的資源是記憶體空間,因為程式是在記憶體中執行的。在程序中,有些程式流程塊是可以亂序執行的,並且這個程式碼塊可以同時被多次執行。實際上,這樣的程式碼塊就是執行緒體。執行緒是程序中亂序執行的程式碼流程。當多個執行緒同時執行的時候,這樣的執行模式成為併發執行。 


  多執行緒的目的是為了最大限度的利用CPU資源。 

  Java編寫程式都執行在在Java虛擬機器(JVM)中,在JVM的內部,程式的多工是通過執行緒來實現的。每用java命令啟動一個java應用程式,就會啟動一個JVM程序。在同一個JVM程序中,有且只有一個程序,就是它自己。在這個JVM環境中,所有程式程式碼的執行都是以執行緒來執行。 

  一般常見的Java應用程式都是單執行緒的。比如,用java命令執行一個最簡單的HelloWorld的Java應用程式時,就啟動了一個JVM程序,JVM找到程式程式的入口點main(),然後執行main()方法,這樣就產生了一個執行緒,這個執行緒稱之為主執行緒。當main方法結束後,主執行緒執行完成。JVM程序也隨即退出。 


  對於一個程序中的多個執行緒來說,多個執行緒共享程序的記憶體塊,當有新的執行緒產生的時候,作業系統不分配新的記憶體,而是讓新執行緒共享原有的程序塊的記憶體。因此,執行緒間的通訊很容易,速度也很快。不同的程序因為處於不同的記憶體塊,因此程序之間的通訊相對困難。 

  實際上,操作的系統的多程序實現了多工併發執行,程式的多執行緒實現了程序的併發執行。多工、多程序、多執行緒的前提都是要求作業系統提供多工、多程序、多執行緒的支援。 

  在Java程式中,JVM負責執行緒的排程。執行緒排程是值按照特定的機制為多個執行緒分配CPU的使用權。 

  排程的模式有兩種:分時排程和搶佔式排程。分時排程是所有執行緒輪流獲得CPU使用權,並平均分配每個執行緒佔用CPU的時間;搶佔式排程是根據執行緒的優先級別來獲取CPU的使用權。JVM的執行緒排程模式採用了搶佔式模式。 


  所謂的“併發執行”、“同時”其實都不是真正意義上的“同時”。眾所周知,CPU都有個時鐘頻率,表示每秒中能執行cpu指令的次數。在每個時鐘週期內,CPU實際上只能去執行一條(也有可能多條)指令。作業系統將程序執行緒進行管理,輪流(沒有固定的順序)分配每個程序很短的一段是時間(不一定是均分),然後在每個執行緒內部,程式程式碼自己處理該程序內部執行緒的時間分配,多個執行緒之間相互的切換去執行,這個切換時間也是非常短的。因此多工、多程序、多執行緒都是作業系統給人的一種巨集觀感受,從微觀角度看,程式的執行是非同步執行的。 

  用一句話做總結:雖然作業系統是多執行緒的,但CPU每一時刻只能做一件事,和人的大腦是一樣的,呵呵。 

Java語言的多執行緒需要作業系統的支援。 

  Java 虛擬機器允許應用程式併發地執行多個執行執行緒。Java語言提供了多執行緒程式設計的擴充套件點,並給出了功能強大的執行緒控制API。 

  在Java中,多執行緒的實現有兩種方式: 

  擴充套件java.lang.Thread類 

  實現java.lang.Runnable介面 

  每個執行緒都有一個優先順序,高優先順序執行緒的執行優先於低優先順序執行緒。每個執行緒都可以或不可以標記為一個守護程式。當某個執行緒中執行的程式碼建立一個新 Thread 物件時,該新執行緒的初始優先順序被設定為建立執行緒的優先順序,並且當且僅當建立執行緒是守護執行緒時,新執行緒才是守護程式。 

  當 Java 虛擬機器啟動時,通常都會有單個非守護執行緒(它通常會呼叫某個指定類的 main 方法)。Java 虛擬機器會繼續執行執行緒,直到下列任一情況出現時為止: 

  呼叫了 Runtime 類的 exit 方法,並且安全管理器允許退出操作發生。 

  非守護執行緒的所有執行緒都已停止執行,無論是通過從對 run 方法的呼叫中返回,還是通過丟擲一個傳播到 run 方法之外的異常。 

擴充套件java.lang.Thread類 

  執行結果: 

  main 執行緒執行開始! 

  main 執行緒執行結束! 

  A 執行緒執行開始! 

  0 A 

  1 A 

  B 執行緒執行開始! 

  2 A 

  0 B 

  3 A 

  4 A 

  1 B 

  5 A 

  6 A 

  7 A 

  8 A 

  9 A 

  A 執行緒執行結束! 

  2 B 

  3 B 

  4 B 

  5 B 

  6 B 

  7 B 

  8 B 

  9 B 

  B 執行緒執行結束! 

  說明: 

  程式啟動執行main時候,java虛擬機器啟動一個程序,主執行緒main在main()呼叫時候被建立。隨著呼叫MitiSay的兩個物件的start方法,另外兩個執行緒也啟動了,這樣,整個應用就在多執行緒下執行。 

  在一個方法中呼叫Thread.currentThread().getName()方法,可以獲取當前執行緒的名字。在mian方法中呼叫該方法,獲取的是主執行緒的名字。 

  注意:start()方法的呼叫後並不是立即執行多執行緒程式碼,而是使得該執行緒變為可執行態(Runnable),什麼時候執行是由作業系統決定的。 

  從程式執行的結果可以發現,多執行緒程式是亂序執行。因此,只有亂序執行的程式碼才有必要設計為多執行緒。 

  Thread.sleep()方法呼叫目的是不讓當前執行緒獨自霸佔該程序所獲取的CPU資源,以留出一定時間給其他執行緒執行的機會。 

  實際上所有的多執行緒程式碼執行順序都是不確定的,每次執行的結果都是隨機的。 

1  public class TestMitiThread { 

3   public static void main(String[] rags) { 

5   System.out.println(Thread.currentThread().getName() + " 執行緒執行開始!"); 

7   new MitiSay("A").start(); 

9   new MitiSay("B").start(); 
10 
11   System.out.println(Thread.currentThread().getName() + " 執行緒執行結束!"); 
12 
13   } 
14 
15   } 
16 
17   class MitiSay extends Thread { 
18 
19   public MitiSay(String threadName) { 
20 
21   super(threadName); 
22 
23   } 
24 
25   public void run() { 
26 
27   System.out.println(getName() + " 執行緒執行開始!"); 
28 
29   for (int i = 0; i < 10; i++) { 
30 
31   System.out.println(i + " " + getName()); 
32 
33   try { 
34 
35   sleep((int) Math.random() * 10); 
36 
37   } catch (InterruptedException e) { 
38 
39   e.printStackTrace(); 
40 
41   } 
42 
43   } 
44 
45   System.out.println(getName() + " 執行緒執行結束!"); 
46 
47   } 
48 
49   } 

實現java.lang.Runnable介面 

  執行結果: 

  main 執行緒執行開始! 

  Thread-0 執行緒執行開始! 

  main 執行緒執行結束! 

  0 Thread-0 

  Thread-1 執行緒執行開始! 

  0 Thread-1 

  1 Thread-1 

  1 Thread-0 

  2 Thread-0 

  2 Thread-1 

  3 Thread-0 

  3 Thread-1 

  4 Thread-0 

  4 Thread-1 

  5 Thread-0 

  6 Thread-0 

  5 Thread-1 

  7 Thread-0 

  8 Thread-0 

  6 Thread-1 

  9 Thread-0 

  7 Thread-1 

  Thread-0 執行緒執行結束! 

  8 Thread-1 

  9 Thread-1 

  Thread-1 執行緒執行結束! 

  說明: 

  TestMitiThread1類通過實現Runnable介面,使得該類有了多執行緒類的特徵。run()方法是多執行緒程式的一個約定。所有的多執行緒程式碼都在run方法裡面。Thread類實際上也是實現了Runnable介面的類。 

  在啟動的多執行緒的時候,需要先通過Thread類的構造方法Thread(Runnable target) 構造出物件,然後呼叫Thread物件的start()方法來執行多執行緒程式碼。 

  實際上所有的多執行緒程式碼都是通過執行Thread的start()方法來執行的。因此,不管是擴充套件Thread類還是實現Runnable介面來實現多執行緒,最終還是通過Thread的物件的API來控制執行緒的,熟悉Thread類的API是進行多執行緒程式設計的基礎。 

1 public class TestMitiThread1 implements Runnable { 

3 public static void main(String[] args) { 
4 System.out.println(Thread.currentThread().getName() + " 執行緒執行開始!"); 
5 TestMitiThread1 test = new TestMitiThread1(); 
6 Thread thread1 = new Thread(test); 
7 Thread thread2 = new Thread(test); 
8 thread1.start(); 
9 thread2.start(); 
10 System.out.println(Thread.currentThread().getName() + " 執行緒執行結束!"); 
11 } 
12 
13 public void run() { 
14 System.out.println(Thread.currentThread().getName() + " 執行緒執行開始!"); 
15 for (int i = 0; i < 10; i++) { 
16 System.out.println(i + " " + Thread.currentThread().getName()); 
17 try { 
18 Thread.sleep((int) Math.random() * 10); 
19 } catch (InterruptedException e) { 
20 e.printStackTrace(); 
21 } 
22 } 
23 System.out.println(Thread.currentThread().getName() + " 執行緒執行結束!"); 
24 } 
25 } 

static int MAX_PRIORITY 

  執行緒可以具有的最高優先順序。 

  static int MIN_PRIORITY 

  執行緒可以具有的最低優先順序。 

  static int NORM_PRIORITY 

  分配給執行緒的預設優先順序。 

  構造方法摘要 

  Thread(Runnable target) 

  分配新的 Thread 物件。 

  Thread(String name) 

  分配新的 Thread 物件。 

  方法摘要 

  static Thread currentThread() 

  返回對當前正在執行的執行緒物件的引用。 

  ClassLoader getContextClassLoader() 

  返回該執行緒的上下文 ClassLoader。 

  long getId() 

  返回該執行緒的識別符號。 

  String getName() 

  返回該執行緒的名稱。 

  int getPriority() 

  返回執行緒的優先順序。 

  Thread.State getState() 

  返回該執行緒的狀態。 

  ThreadGroup getThreadGroup() 

  返回該執行緒所屬的執行緒組。 

  static boolean holdsLock(Object obj) 

  當且僅當當前執行緒在指定的物件上保持監視器鎖時,才返回 true。 

  void interrupt() 

  中斷執行緒。 

  static boolean interrupted() 

  測試當前執行緒是否已經中斷。 

  boolean isAlive() 

  測試執行緒是否處於活動狀態。 

  boolean isDaemon() 

  測試該執行緒是否為守護執行緒。 

  boolean isInterrupted() 

  測試執行緒是否已經中斷。 

  void join() 

  等待該執行緒終止。 

  void join(long millis) 

  等待該執行緒終止的時間最長為 millis 毫秒。 

  void join(long millis, int nanos) 

  等待該執行緒終止的時間最長為 millis 毫秒 + nanos 納秒。 

  void resume() 

  已過時。 該方法只與 suspend() 一起使用,但 suspend() 已經遭到反對,因為它具有死鎖傾向。有關更多資訊,請參閱為何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。 

  void run() 

  如果該執行緒是使用獨立的 Runnable 執行物件構造的,則呼叫該 Runnable 物件的 run 方法;否則,該方法不執行任何操作並返回。 

  void setContextClassLoader(ClassLoader cl) 

  設定該執行緒的上下文 ClassLoader。 

  void setDaemon(boolean on) 

  將該執行緒標記為守護執行緒或使用者執行緒。 

  static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 

  設定當執行緒由於未捕獲到異常而突然終止,並且沒有為該執行緒定義其他處理程式時所呼叫的預設處理程式。 

  void setName(String name) 

  改變執行緒名稱,使之與引數 name 相同。 

  void setPriority(int newPriority) 

  更改執行緒的優先順序。 

  void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 

  設定該執行緒由於未捕獲到異常而突然終止時呼叫的處理程式。 

  static void sleep(long millis) 

  在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行)。 

  static void sleep(long millis, int nanos) 

  在指定的毫秒數加指定的納秒數內讓當前正在執行的執行緒休眠(暫停執行)。 

  void start() 

  使該執行緒開始執行;Java 虛擬機器呼叫該執行緒的 run 方法。 

  void stop() 

  已過時。 該方法具有固有的不安全性。用 Thread.stop 來終止執行緒將釋放它已經鎖定的所有監視器(作為沿堆疊向上傳播的未檢查 ThreadDeath 異常的一個自然後果)。如果以前受這些監視器保護的任何物件都處於一種不一致的狀態,則損壞的物件將對其他執行緒可見,這有可能導致任意的行為。stop 的許多使用都應由只修改某些變數以指示目標執行緒應該停止執行的程式碼來取代。目標執行緒應定期檢查該變數,並且如果該變數指示它要停止執行,則從其執行方法依次返回。如果目標執行緒等待很長時間(例如基於一個條件變數),則應使用 interrupt 方法來中斷該等待。有關更多資訊,請參閱《為何不贊成使用 Thread.stop、Thread.suspend 和 Thread.resume?》。 

  void stop(Throwable obj) 

  已過時。 該方法具有固有的不安全性。請參閱 stop() 以獲得詳細資訊。該方法的附加危險是它可用於生成目標執行緒未準備處理的異常(包括若沒有該方法該執行緒不太可能丟擲的已檢查的異常)。有關更多資訊,請參閱為何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。 

  void suspend() 

  已過時。該方法已經遭到反對,因為它具有固有的死鎖傾向。如果目標執行緒掛起時在保護關鍵系統資源的監視器上保持有鎖,則在目標執行緒重新開始以前任何執行緒都不能訪問該資源。如果重新開始目標執行緒的執行緒想在呼叫 resume 之前鎖定該監視器,則會發生死鎖。這類死鎖通常會證明自己是“凍結”的程序。有關更多資訊,請參閱為何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。 

  String toString() 

  返回該執行緒的字串表示形式,包括執行緒名稱、優先順序和執行緒組。 

  static void yield() 

  暫停當前正在執行的執行緒物件,並執行其他執行緒。 

執行緒在一定條件下,狀態會發生變化。執行緒變化的狀態轉換圖如下: 

  1、新建狀態(New):新建立了一個執行緒物件。 

  2、就緒狀態(Runnable):執行緒物件建立後,其他執行緒呼叫了該物件的start()方法。該狀態的執行緒位於可執行執行緒池中,變得可執行,等待獲取CPU的使用權。 

  3、執行狀態(Running):就緒狀態的執行緒獲取了CPU,執行程式程式碼。 

  4、阻塞狀態(Blocked):阻塞狀態是執行緒因為某種原因放棄CPU使用權,暫時停止執行。直到執行緒進入就緒狀態,才有機會轉到執行狀態。阻塞的情況分三種: 

  (一)、等待阻塞:執行的執行緒執行wait()方法,JVM會把該執行緒放入等待池中。 

  (二)、同步阻塞:執行的執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒佔用,則JVM會把該執行緒放入鎖池中。 

  (三)、其他阻塞:執行的執行緒執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該執行緒置為阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。 

  5、死亡狀態(Dead):執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。 

調整執行緒優先順序:Java執行緒有優先順序,優先順序高的執行緒會獲得較多的執行機會。 

  Java執行緒的優先順序用整數表示,取值範圍是1~10,Thread類有以下三個靜態常量: 

  static int MAX_PRIORITY 

  執行緒可以具有的最高優先順序,取值為10。 

  static int MIN_PRIORITY 

  執行緒可以具有的最低優先順序,取值為1。 

  static int NORM_PRIORITY 

  分配給執行緒的預設優先順序,取值為5。 

  Thread類的setPriority()和getPriority()方法分別用來設定和獲取執行緒的優先順序。 

  每個執行緒都有預設的優先順序。主執行緒的預設優先順序為Thread.NORM_PRIORITY。 

  執行緒的優先順序有繼承關係,比如A執行緒中建立了B執行緒,那麼B將和A具有相同的優先順序。 

  JVM提供了10個執行緒優先順序,但與常見的作業系統都不能很好的對映。如果希望程式能移植到各個作業系統中,應該僅僅使用Thread類有以下三個靜態常量作為優先順序,這樣能保證同樣的優先順序採用了同樣的排程方式。 

  2、執行緒睡眠:Thread.sleep(long millis)方法,使執行緒轉到阻塞狀態。millis引數設定睡眠的時間,以毫秒為單位。當睡眠結束後,就轉為就緒(Runnable)狀態。sleep()平臺移植性好。 

  3、執行緒等待:Object類中的wait()方法,導致當前的執行緒等待,直到其他執行緒呼叫此物件的 notify() 方法或 notifyAll() 喚醒方法。這個兩個喚醒方法也是Object類中的方法,行為等價於呼叫 wait(0) 一樣。 

  4、執行緒讓步:Thread.yield() 方法,暫停當前正在執行的執行緒物件,把執行機會讓給相同或者更高優先順序的執行緒。 

  5、執行緒加入:join()方法,等待其他執行緒終止。在當前執行緒中呼叫另一個執行緒的join()方法,則當前執行緒轉入阻塞狀態,直到另一個程序執行結束,當前執行緒再由阻塞轉為就緒狀態。 

  6、執行緒喚醒:Object類中的notify()方法,喚醒在此物件監視器上等待的單個執行緒。如果所有執行緒都在此物件上等待,則會選擇喚醒其中一個執行緒。選擇是任意性的,並在對實現做出決定時發生。執行緒通過呼叫其中一個 wait 方法,在物件的監視器上等待。直到當前的執行緒放棄此物件上的鎖定,才能繼續執行被喚醒的執行緒。被喚醒的執行緒將以常規方式與在該物件上主動同步的其他所有執行緒進行競爭;例如,喚醒的執行緒在作為鎖定此物件的下一個執行緒方面沒有可靠的特權或劣勢。類似的方法還有一個notifyAll(),喚醒在此物件監視器上等待的所有執行緒。 

  注意:Thread中suspend()和resume()兩個方法在JDK1.5中已經廢除,不再介紹。因為有死鎖傾向。 

  7、常見執行緒名詞解釋 

  主執行緒:JVM呼叫程式mian()所產生的執行緒。 

  當前執行緒:這個是容易混淆的概念。一般指通過Thread.currentThread()來獲取的程序。 

  後臺執行緒:指為其他執行緒提供服務的執行緒,也稱為守護執行緒。JVM的垃圾回收執行緒就是一個後臺執行緒。 

  前臺執行緒:是指接受後臺執行緒服務的執行緒,其實前臺後臺執行緒是聯絡在一起,就像傀儡和幕後操縱者一樣的關係。傀儡是前臺執行緒、幕後操縱者是後臺執行緒。由前臺執行緒建立的執行緒預設也是前臺執行緒。可以通過isDaemon()和setDaemon()方法來判斷和設定一個執行緒是否為後臺執行緒。