【Java基本功】一文了解Java多執行緒的基礎知識點
- 2019 年 10 月 7 日
- 筆記
本文介紹了Java多執行緒的基本概念,使用方法,以及底層實現原理。幫助你更好地使用Java的多執行緒。
Java中的執行緒
Java之父對執行緒的定義是:
執行緒是一個獨立執行的調用序列,同一個進程的執行緒在同一時刻共享一些系統資源(比如文件句柄等)也能訪問同一個進程所創建的對象資源(記憶體資源)。java.lang.Thread對象負責統計和控制這種行為。 每個程式都至少擁有一個執行緒-即作為Java虛擬機(JVM)啟動參數運行在主類main方法的執行緒。在Java虛擬機初始化過程中也可能啟動其他的後台執行緒。這種執行緒的數目和種類因JVM的實現而異。然而所有用戶級執行緒都是顯式被構造並在主執行緒或者是其他用戶執行緒中被啟動。
本文主要講了java中多執行緒的使用方法、執行緒同步、執行緒數據傳遞、執行緒狀態及相應的一些執行緒函數用法、概述等。在這之前,首先讓我們來了解下在作業系統中進程和執行緒的區別: 進程:每個進程都有獨立的程式碼和數據空間(進程上下文),進程間的切換會有較大的開銷,一個進程包含1--n個執行緒。(進程是資源分配的最小單位) 執行緒:同一類執行緒共享程式碼和數據空間,每個執行緒有獨立的運行棧和程式計數器(PC),執行緒切換開銷小。(執行緒是cpu調度的最小單位) 執行緒和進程一樣分為五個階段:創建、就緒、運行、阻塞、終止。 多進程是指作業系統能同時運行多個任務(程式)。 多執行緒是指在同一程式中有多個順序流在執行。 在java中要想實現多執行緒,有兩種手段,一種是繼續Thread類,另外一種是實現Runable介面.(其實準確來講,應該有三種,還有一種是實現Callable介面,並與Future、執行緒池結合使用
Java執行緒記憶體模型
下面的圖大致介紹了Java執行緒的調用過程,每個執行緒使用一個獨立的調用棧進行執行緒執行,棧中的數據不共享,堆區和方法區的數據是共享的。



構造方法和守護執行緒
構造方法 Thread類中不同的構造方法接受如下參數的不同組合: 一個Runnable對象,這種情況下,Thread.start方法將會調用對應Runnable對象的run方法。如果沒有提供Runnable對象,那麼就會立即得到一個Thread.run的默認實現。 一個作為執行緒標識名的String字元串,該標識在跟蹤和調試過程中會非常有用,除此別無它用。 執行緒組(ThreadGroup),用來放置新創建的執行緒,如果提供的ThreadGroup不允許被訪問,那麼就會拋出一個SecurityException 。 Thread對象擁有一個守護(daemon)標識屬性,這個屬性無法在構造方法中被賦值,但是可以在執行緒啟動之前設置該屬性(通過setDaemon方法)。 當程式中所有的非守護執行緒都已經終止,調用setDaemon方法可能會導致虛擬機粗暴的終止執行緒並退出。 isDaemon方法能夠返回該屬性的值。守護狀態的作用非常有限,即使是後台執行緒在程式退出的時候也經常需要做一些清理工作。 (daemon的發音為」day-mon」,這是系統編程傳統的遺留,系統守護進程是一個持續運行的進程,比如印表機隊列管理,它總是在系統中運行。)
啟動執行緒的方式和isAlive方法
啟動執行緒 調用start方法會觸發Thread實例以一個新的執行緒啟動其run方法。新執行緒不會持有調用執行緒的任何同步鎖。
當一個執行緒正常地運行結束或者拋出某種未檢測的異常(比如,運行時異常(RuntimeException),錯誤(ERROR) 或者其子類)執行緒就會終止。
當執行緒終止之後,是不能被重新啟動的。在同一個Thread上調用多次start方法會拋出InvalidThreadStateException異常。
如果執行緒已經啟動但是還沒有終止,那麼調用isAlive方法就會返回true.即使執行緒由於某些原因處於阻塞(Blocked)狀態該方法依然返回true。
如果執行緒已經被取消(cancelled),那麼調用其isAlive在什麼時候返回false就因各Java虛擬機的實現而異了。沒有方法可以得知一個處於非活動狀態的執行緒是否已經被啟動過了。
優先順序
Java的執行緒實現基本上都是內核級執行緒的實現,所以Java執行緒的具體執行還取決於作業系統的特性。
Java虛擬機為了實現跨平台(不同的硬體平台和各種作業系統)的特性,Java語言在執行緒調度與調度公平性上未作出任何的承諾,甚至都不會嚴格保證執行緒會被執行。但是Java執行緒卻支援優先順序的方法,這些方法會影響執行緒的調度:
每個執行緒都有一個優先順序,分布在Thread.MINPRIORITY和Thread.MAXPRIORITY之間(分別為1和10) 默認情況下,新創建的執行緒都擁有和創建它的執行緒相同的優先順序。main方法所關聯的初始化執行緒擁有一個默認的優先順序,這個優先順序是Thread.NORM_PRIORITY (5).
執行緒的當前優先順序可以通過getPriority方法獲得。 執行緒的優先順序可以通過setPriority方法來動態的修改,一個執行緒的最高優先順序由其所在的執行緒組限定。
執行緒的控制方法
只有很少幾個方法可以用於跨執行緒交流:
每個執行緒都有一個相關的Boolean類型的中斷標識。在執行緒t上調用t.interrupt會將該執行緒的中斷標識設為true,除非執行緒t正處於Object.wait,Thread.sleep,或者Thread.join,這些情況下interrupt調用會導致t上的這些操作拋出InterruptedException異常,但是t的中斷標識會被設為false。 任何一個執行緒的中斷狀態都可以通過調用isInterrupted方法來得到。如果執行緒已經通過interrupt方法被中斷,這個方法將會返回true。 但是如果調用了Thread.interrupted方法且中斷標識還沒有被重置,或者是執行緒處於wait,sleep,join過程中,調用isInterrupted方法將會拋出InterruptedException異常。 調用t.join()方法將會暫停執行調用執行緒,直到執行緒t執行完畢:當t.isAlive()方法返回false的時候調用t.join()將會直接返回(return)。 另一個帶參數毫秒(millisecond)的join方法在被調用時,如果執行緒沒能夠在指定的時間內完成,調用執行緒將重新得到控制權。 因為isAlive方法的實現原理,所以在一個還沒有啟動的執行緒上調用join方法是沒有任何意義的。同樣的,試圖在一個還沒有創建的執行緒上調用join方法也是不明智的。 起初,Thread類還支援一些另外一些控制方法:suspend,resume,stop以及destroy。這幾個方法已經被聲明過期。其中destroy方法從來沒有被實現,估計以後也不會。而通過使用等待/喚醒機制增加suspend和resume方法在安全性和可靠性的效果有所欠缺
Thread的靜態方法
靜態方法 Thread類中的部分方法被設計為只適用於當前正在運行的執行緒(即調用Thread方法的執行緒)。為強調這點,這些方法都被聲明為靜態的。 Thread.currentThread方法會返回當前執行緒的引用,得到這個引用可以用來調用其他的非靜態方法,比如Thread.currentThread().getPriority()會返回調用執行緒的優先順序。 Thread.interrupted方法會清除當前執行緒的中斷狀態並返回前一個狀態。(一個執行緒的中斷狀態是不允許被其他執行緒清除的) Thread.sleep(long msecs)方法會使得當前執行緒暫停執行至少msecs毫秒。 Thread.yield方法純粹只是建議Java虛擬機對其他已經處於就緒狀態的執行緒(如果有的話)調度執行,而不是當前執行緒。最終Java虛擬機如何去實現這種行為就完全看其喜好了。
執行緒組
每一個執行緒都是一個執行緒組中的成員。默認情況下,新建執行緒和創建它的執行緒屬於同一個執行緒組。執行緒組是以樹狀分布的。 當創建一個新的執行緒組,這個執行緒組成為當前執行緒組的子組。getThreadGroup方法會返回當前執行緒所屬的執行緒組,對應地,ThreadGroup類也有方法可以得到哪些執行緒目前屬於這個執行緒組,比如enumerate方法。 ThreadGroup類存在的一個目的是支援安全策略來動態的限制對該組的執行緒操作。比如對不屬於同一組的執行緒調用interrupt是不合法的。 這是為避免某些問題(比如,一個applet執行緒嘗試殺掉主螢幕的刷新執行緒)所採取的措施。ThreadGroup也可以為該組所有執行緒設置一個最大的執行緒優先順序。 執行緒組往往不會直接在程式中被使用。在大多數的應用中,如果僅僅是為在程式中跟蹤執行緒對象的分組,那麼普通的集合類(比如java.util.Vector)應是更好的選擇。
多執行緒的實現
public class 多執行緒實例 { //繼承thread @Test public void test1() { class A extends Thread { @Override public void run() { System.out.println("A run"); } } A a = new A(); a.start(); } //實現Runnable @Test public void test2() { class B implements Runnable { @Override public void run() { System.out.println("B run"); } } B b = new B(); //Runable實現類需要由Thread類包裝後才能執行 new Thread(b).start(); } //有返回值的執行緒 @Test public void test3() { Callable callable = new Callable() { int sum = 0; @Override public Object call() throws Exception { for (int i = 0;i < 5;i ++) { sum += i; } return sum; } }; //這裡要用FutureTask,否則不能加入Thread構造方法 FutureTask futureTask = new FutureTask(callable); new Thread(futureTask).start(); try { System.out.println(futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } //執行緒池實現 @Test public void test4() { ExecutorService executorService = Executors.newFixedThreadPool(5); //execute直接執行執行緒 executorService.execute(new Thread()); executorService.execute(new Runnable() { @Override public void run() { System.out.println("runnable"); } }); //submit提交有返回結果的任務,運行完後返回結果。 Future future = executorService.submit(new Callable<String>() { @Override public String call() throws Exception { return "a"; } }); try { System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } ArrayList<String> list = new ArrayList<>(); //有返回值的執行緒組將返回值存進集合 for (int i = 0;i < 5;i ++ ) { int finalI = i; Future future1 = executorService.submit(new Callable<String>() { @Override public String call() throws Exception { return "res" + finalI; } }); try { list.add((String) future1.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } for (String s : list) { System.out.println(s); } } }執行緒狀態轉換
public class 執行緒的狀態轉換 { //一開始執行緒是init狀態,結束時是terminated狀態 class t implements Runnable { private String name; public t(String name) { this.name = name; } @Override public void run() { System.out.println(name + "run"); } } //測試join,父執行緒在子執行緒運行時進入waiting狀態 @Test public void test1() throws InterruptedException { Thread dad = new Thread(new Runnable() { Thread son = new Thread(new t("son")); @Override public void run() { System.out.println("dad init"); son.start(); try { //保證子執行緒運行完再運行父執行緒 son.join(); System.out.println("dad run"); } catch (InterruptedException e) { e.printStackTrace(); } } }); //調用start,執行緒進入runnable狀態,等待系統調度 dad.start(); //在父執行緒中對子執行緒實例使用join,保證子執行緒在父執行緒之前執行完 } //測試sleep @Test public void test2(){ Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("t1 run"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }); //主執行緒休眠。進入time waiting狀態 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } t1.start(); } //執行緒2進入blocked狀態。 public static void main(String[] args) { test4(); Thread.yield();//進入runnable狀態 } //測試blocked狀態 public static void test4() { class A { //執行緒1獲得實例鎖以後執行緒2無法獲得實例鎖,所以進入blocked狀態 synchronized void run() { while (true) { System.out.println("run"); } } } A a = new A(); new Thread(new Runnable() { @Override public void run() { System.out.println("t1 get lock"); a.run(); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println("t2 get lock"); a.run(); } }).start(); } //volatile保證執行緒可見性 volatile static int flag = 1; //object作為鎖對象,用於執行緒使用wait和notify方法 volatile static Object o = new Object(); //測試wait和notify //wait後進入waiting狀態,被notify進入blocked(阻塞等待鎖釋放)或者runnable狀態(獲取到鎖) public void test5() { new Thread(new Runnable() { @Override public void run() { //wait和notify只能在同步程式碼塊內使用 synchronized (o) { while (true) { if (flag == 0) { try { Thread.sleep(2000); System.out.println("thread1 wait"); //釋放鎖,執行緒掛起進入object的等待隊列,後續程式碼運行 o.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("thread1 run"); System.out.println("notify t2"); flag = 0; //通知等待隊列的一個執行緒獲取鎖 o.notify(); } } } }).start(); //解釋同上 new Thread(new Runnable() { @Override public void run() { while (true) { synchronized (o) { if (flag == 1) { try { Thread.sleep(2000); System.out.println("thread2 wait"); o.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("thread2 run"); System.out.println("notify t1"); flag = 1; o.notify(); } } } }).start(); } //輸出結果是 // thread1 run // notify t2 // thread1 wait // thread2 run // notify t1 // thread2 wait // thread1 run // notify t2 //不斷循環 }