多執行緒入門
本次主要內容,主要是初步瞭解執行緒,建立執行緒,使用一些簡單的API,多執行緒的五種狀態。
執行緒和程序
什麼是執行緒?什麼是程序?執行緒和程序的區別是什麼?(面試常問)
用例子說明:我們開啟電腦,同時開啟qq,網易雲音樂,word多個軟體,在工作管理員中就可以看到這些就是程序, 程序是正在執行中的程式 ,我們在qq中,既可以給好友發信息,發文件,也可以接收資訊,這些就是這個程序中的執行緒, 執行緒是正在獨立執行的一條執行路徑,程序是執行緒的集合,一個作業系統可以有多個程序,一個程序有多個執行緒。 任何一個程序都有一個主執行緒,在Java中是main執行緒。
為什麼使用多執行緒?
百度網盤下載,我們可以同時下載多個檔案,這多個檔案是交給執行緒處理的,並且執行緒之間是互不影響的,不會因為一個出問題了,影響到其他檔案的下載, 提高程式的效率。
如何建立多執行緒
有幾種方式?共有5種
在這裡先介紹三種:
1、繼承Thread類
2、實現Runnable介面
3、使用匿名內部類(可以算作一種)
另外兩種後面深入再講:
1、callable
2、使用執行緒池建立執行緒
01繼承Thread類
class CreateThread1 extends Thread { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("run i:" + i); } } } public class Thread_Demo1 { public static void main(String[] args) { // 呼叫執行緒 CreateThread1 ct1 = new CreateThread1(); // 啟動執行緒 ct1.start(); for (int i = 0; i < 30; i++) { System.out.println("main i:" + i);//列印交替執行 } } }
繼承Thread類建立執行緒,重寫run方法,呼叫執行緒就是建立執行緒物件。怎麼啟動執行緒?不是直接物件名.run(),而是呼叫start方法來啟動執行緒,如果呼叫run,就成了簡單的呼叫一個普通的run方法。一旦開啟執行緒以後,程式碼的執行順序不會在按照從上往下的順序執行。執行順序具有隨機性。上面的程式碼執行一次的部分結果為:
main i:0 main i:1 run i:0 main i:2 run i:1 main i:3 run i:2 main i:4 run i:3 run i:4 main i:5
main是主執行緒,很容易的看出,多執行緒執行順序不是從上而下的。
上圖明顯看出執行緒和多執行緒之間的區別,如果每個任務完成需要10秒,那麼單執行緒下需要20秒,多執行緒下只需要10秒,單執行緒下如果任務1執行過程中出錯了,那麼整個程式就不會繼續執行,多執行緒下互不影響。
這裡,我們引出同步和非同步的概念,同步就是程式碼從上往下順序執行,是單執行緒的,非同步就不是順序執行,多執行緒,並且執行緒之間互不影響。
02實現Runnable介面
class CreateThread2 implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("run i:" + i); } } } public class Thread_Demo2 { public static void main(String[] args) { // 呼叫執行緒 CreateThread2 ct2 = new CreateThread2(); Thread t = new Thread(ct2);//別名 new Thread(ct2,"別名"); // 啟動執行緒 t.start(); for (int i = 0; i < 30; i++) { System.out.println("main i:" + i);//列印交替執行 } } }
原始碼中,Thread類是實現了Runnable介面的,我們需要先建立實現Runnable的物件,然後傳給Thread,啟動執行緒。
對於這兩種方法,是繼承好還是實現介面好?
實現介面好,Java是單繼承,多實現的面向物件程式語言,並且後面的開發都是面向介面程式設計,有利於程式碼的擴充套件與維護。
03匿名內部類建立執行緒
public class Thread_Demo3 { public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 30; i++) { System.out.println("run i:"+i); } } }); t.start(); for (int i = 0; i < 30; i++) { System.out.println("main i:" + i);//列印交替執行 } } }
簡單的API
1、start() 啟動執行緒
2、currentThread() 獲取當前執行緒物件
3、getID() 獲取當前執行緒ID Thread-編號,編號從0開始
4、getName() 獲取當前執行緒名稱
5、sleep() 執行緒休眠
6、stop() 停止執行緒(已過時,不建議使用)
這些大部分都是靜態方法,可以直接Thread.進行呼叫。
class CreateT extends Thread { @Override public void run() { for (int i = 0; i < 20; i++) { try { Thread.sleep(1000);//休眠1000毫秒也就是1秒 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("執行緒ID:" + this.getId() + "子執行緒 i:" + i+"子執行緒name:"+this.getName());// 拿到執行緒ID,唯一,不會重複 //Thread.currentThread()獲取當前執行緒 if(i==5){ //Thread.currentThread().stop();不安全,不建議使用,強制停止 } } } } public class ThreadAPI { public static void main(String[] args) { // 獲取主執行緒ID System.out.println("主執行緒ID:" + Thread.currentThread().getId()+"主執行緒name:"+Thread.currentThread().getName()); for (int i = 0; i < 3; i++) { CreateT t = new CreateT(); t.start(); } CreateT t = new CreateT(); t.start(); } }
守護執行緒和非守護執行緒
守護執行緒就是和main執行緒相關的,特徵就是和主執行緒一起銷燬,比如gc執行緒。
非守護執行緒特徵就是和main執行緒互不影響,比如使用者自己建立的使用者執行緒,主執行緒停止掉,不會影響使用者執行緒。
下面這個例子,主執行緒執行完畢後,不會影響到子執行緒,子執行緒依然在執行。
public class Demo4 { public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 20; i++){ System.out.println("子執行緒 i:"+i); } } }); t.start(); for (int i = 0; i < 5; i++) { System.out.println("主執行緒 i:"+i); } System.out.println("主執行緒執行完畢"); } }
使用下面這個方法:
setDaemon(true);//設定該執行緒為守護執行緒,和main執行緒一起掛掉
public class Demo4 { public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 20; i++){ System.out.println("子執行緒 i:"+i); } } }); t.setDaemon(true); t.start(); for (int i = 0; i < 5; i++) { System.out.println("主執行緒 i:"+i); } System.out.println("主執行緒執行完畢"); } }
一次的執行結果
主執行緒 i:0 子執行緒 i:0 主執行緒 i:1 主執行緒 i:2 主執行緒 i:3 主執行緒 i:4 主執行緒執行完畢
主執行緒執行完畢後,子執行緒不會執行完畢,而是會隨著主執行緒的停止就停止了。
join
join的作用就是讓其他執行緒變為等待。
一個A執行緒,一個B執行緒,A執行緒呼叫B執行緒的join方法,那麼A等待B執行完畢後,釋放CPU執行權,再繼續執行。
有這樣一個題目:T1,T2,T3三個執行緒,怎麼保證T2在T1完成後執行,T3在T2完成後執行???
在T3的run方法中呼叫T2的join方法,在T2的run方法中呼叫T1的join方法
public class Join_Test { public static void main(String[] args) { Thread T1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++){ System.out.println("T1"); } } }); T1.start(); Thread T2 = new Thread(new Runnable() { @Override public void run() { try { T1.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } for (int i = 0; i < 5; i++){ System.out.println("T2"); } } }); T2.start(); Thread T3 = new Thread(new Runnable() { @Override public void run() { try { T2.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } for (int i = 0; i < 5; i++){ System.out.println("T3"); } } }); T3.start(); } }
執行緒的幾種狀態
執行緒共有5種狀態。
新建狀態,就緒狀態,執行狀態,阻塞狀態,死亡狀態。
新建狀態:就是new建立一個執行緒,執行緒還沒有開始執行。
就緒狀態:新建狀態的執行緒不會自動執行,要想執行執行緒,需要呼叫start方法啟動執行緒,start執行緒建立執行緒執行的系統資源,並排程執行緒執行run方法,start方法返回後,就處於了就緒狀態。就緒狀態的執行緒不會立即執行run方法,需要獲得CPU時間片後,才會執行。
執行狀態:獲取CPU時間片,執行run方法。
阻塞狀態:執行緒執行時,可能會有好多原因進入該狀態,比如呼叫sleep方法休眠,I/O阻塞,執行緒想得到一個被其他執行緒正在持有的鎖。
死亡狀態:正常退出而死亡,非正常退出而死亡(一個未捕獲的異常終止了該執行緒)。