Java多執行緒——多執行緒方法詳解
本系列文章是Java多執行緒的詳解介紹,對多執行緒還不熟悉的同學可以先去看一下我的這篇部落格Java基礎系列3:多執行緒超詳細總結,這篇部落格從巨集觀層面介紹了多執行緒的整體概況,接下來的幾篇文章是對多執行緒的深入剖析。
多執行緒的常用方法
1、currentThread()方法:
介紹:currentThread()方法可返回該程式碼正在被哪個執行緒呼叫的資訊。
示例:
例1:
public class Test01 { public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); } } 結果: main
結果說明,main方法被名為main的執行緒呼叫
例2:
class Mythread extends Thread{ public Mythread() { System.out.println("構造方法的列印:"+Thread.currentThread().getName()); } @Override public void run() { System.out.println("run方法的列印:"+Thread.currentThread().getName()); } } public class Test01 { public static void main(String[] args) { Mythread t=new Mythread(); t.start();//① } } 結果: 構造方法的列印:main run方法的列印:Thread-0
從結果可知:Mythread的構造方法是被main執行緒呼叫的,而run方法是被名稱為Thread-0的執行緒呼叫的,run方法是執行緒自動呼叫的
現在我們將①處的程式碼改為t.run(),現在的輸出結果如下:
構造方法的列印:main run方法的列印:main
從結果中我們可以看到兩次的結果顯示都是main執行緒呼叫了方法,因為當你使用t.start()方法的時候是執行緒自動呼叫的run()方法,所以輸出的是Thread-0,當你直接呼叫run()方法時,和呼叫普通方法沒有什麼區別,所以是main執行緒呼叫run()
2、isAlive()方法:
介紹:isAlive()方法的功能是判斷當前的執行緒是否處於活動狀態
示例:
例1:
class Mythread extends Thread{ @Override public void run() { System.out.println("run =="+this.isAlive()); } } public class Test01 { public static void main(String[] args) { Mythread thread=new Mythread(); System.out.println("begin =="+thread.isAlive());//① thread.start();//② System.out.println("end =="+thread.isAlive());//③ } } 結果: begin ==false end ==true run ==true
方法isAlive()的作用是測試執行緒是否處於活動狀態。那麼什麼情況下是活動狀態呢?活動狀態就是執行緒已經啟動且尚未停止。執行緒處於正在執行或準備開始執行的狀態,就認為執行緒是存活的
①處程式碼的結果為false,因為此時執行緒還未啟動;
②處程式碼呼叫了run()方法輸出結果為run ==true,此時執行緒處於活動狀態;
③處程式碼的結果為true,有的同學看到這個輸出可能會不理解,不是說執行緒處於活動狀態isAlive()方法的結果才是true,現在程式都已經執行結束了為什麼還是true?這裡的輸出結果是不確定的,我們再來看下面一段程式碼
我們將例1中的程式碼稍做修改,程式碼如下:
public class Test01 { public static void main(String[] args) throws InterruptedException { Mythread thread=new Mythread(); System.out.println("begin =="+thread.isAlive());//① thread.start();//② Thread.sleep(1000);//這裡加了一行程式碼,讓當前執行緒沉睡1秒 System.out.println("end =="+thread.isAlive());//③ } } 結果: begin ==false run ==true end ==false
現在我們看到③處的程式碼結果為end ==false,因為thread物件已經在1秒內執行完畢,而上面程式碼輸出結果為true是因為thread執行緒未執行完畢。
3、sleep()方法:
介紹:
方法sleep()的作用是在指定的毫秒數內讓當前“正在執行的執行緒”休眠(暫停執行),這個“正在執行的執行緒”是指this.currentThread()返回的執行緒。
示例:
class Mythread extends Thread{ @Override public void run() { try { System.out.println("run threadName="+this.currentThread().getName()+" begin"); Thread.sleep(2000); System.out.println("run threadName="+this.currentThread().getName()+" end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public class Test01 { public static void main(String[] args) throws InterruptedException { Mythread thread=new Mythread(); System.out.println("begin ="+System.currentTimeMillis()); thread.run();//① System.out.println("end ="+System.currentTimeMillis()); } } 結果: begin =1574660731663 run threadName=main begin run threadName=main end end =1574660733665
從結果中可以看出main執行緒暫停了2秒(因為這裡呼叫的是thread.run())
下面我們將①處的程式碼改成thread.start(),再來看下執行結果:
begin =1574661491412 end =1574661491412 run threadName=Thread-0 begin run threadName=Thread-0 end
由於main執行緒與thread執行緒是非同步執行的,所以首先列印的資訊為begin和end,而thread執行緒是隨後執行的,在最後兩行列印run begin和run end的資訊。
4、getId()方法:
介紹:getId()方法的作用是取得執行緒的唯一標識
示例:
public class Test01 { public static void main(String[] args) throws InterruptedException { Thread thread=Thread.currentThread(); System.out.println(thread.getName()+" "+thread.getId()); } }
結果:main 1
從執行結果可以看出,當前執行程式碼的執行緒名稱是main,執行緒id值為1
5、停止執行緒:
介紹:停止執行緒是在多執行緒開發時很重要的技術點,掌握此技術可以對執行緒的停止進行有效的處理。停止執行緒在Java語言中並不像break語句那樣乾脆,需要一些技巧性的處理。
在java中有三種方法可以停止執行緒
- 使用退出標誌,讓執行緒正常退出,也就是當run方法執行完之後終止
- 使用stop方法強制終止執行緒,但是不推薦使用,因為stop和suspend及resume一樣,是java廢棄的方法
- 使用interrupt方法中斷執行緒(推薦使用)
示例:
例1:
class Mythread extends Thread{ @Override public void run() { for(int i=0;i<5000;i++) { System.out.println("i="+(i+1)); } } } public class Test01 { public static void main(String[] args) throws InterruptedException { Mythread thread=new Mythread(); thread.start(); Thread.sleep(2000); thread.interrupt(); } }
執行結果:
從執行結果我們可以看出最後i=500000,呼叫interrupt方法沒有停止執行緒,那麼該如何停止執行緒呢?
在介紹如何停止執行緒時,我們先來介紹一下如何判斷執行緒是否處於停止狀態
Thread類中提供了兩種方法用來判斷執行緒是否停止:
1、this.interrupted():測試當前執行緒是否已經中斷,執行後具有將狀態標誌清除為false的功能
public static boolean interrupted() { return currentThread().isInterrupted(true); }
2、this.isInterrupted():測試執行緒Thread物件是否已經中斷,但是不清除狀態標誌
public boolean isInterrupted() { return isInterrupted(false); }
讀者可以仔細觀看一下這兩個方法的宣告有什麼不同?
例2:
class Mythread extends Thread{ @Override public void run() { for(int i=0;i<5000;i++) { System.out.println("i="+(i+1)); } } } public class Test01 { public static void main(String[] args) throws InterruptedException { Mythread thread=new Mythread(); thread.start(); Thread.sleep(1000); thread.interrupt(); System.out.println("是否停止1?="+thread.interrupted()); System.out.println("是否停止2?="+thread.interrupted()); System.out.println("end!"); } }
結果:
輸出結果顯示呼叫了thread.interrupt()方法後執行緒並未停止,這也就證明了interrupted()方法的解釋:測試當前執行緒是否已經中斷。這個當前執行緒是main,它從未斷過,所以列印的結果是兩個false。
如果想讓main執行緒結束該怎麼做?
將main方法改成如下:
public class Test01 { public static void main(String[] args) throws InterruptedException { Thread.currentThread().interrupt(); System.out.println("是否停止1?="+Thread.interrupted()); System.out.println("是否停止2?="+Thread.interrupted()); System.out.println("end!"); } } 結果: 是否停止1?=true 是否停止2?=false end!
從輸出結果我們可以看出,方法interrupted()的確判斷出當前執行緒是否是停止狀態。但為什麼第2個值是false?
檢視一下官方文件的介紹:
測試當前執行緒是否已經中斷。執行緒的中斷狀態由該方法清除。換句話說,如果連續兩次呼叫該方法,則第二次呼叫將返回false(在第一次呼叫已清除了其中斷狀態之後,且第二次呼叫檢驗完中斷狀態前,當前執行緒再次中斷的情況除外)。
文件中說明的非常清楚,interrupted()方法具有清除狀態的功能,所以第二次呼叫interrupted方法返回的值時false。
下面我們來看一下isInterrupted()方法,將main方法改成如下程式碼:
public static void main(String[] args) throws InterruptedException { Mythread thread=new Mythread(); thread.start(); thread.interrupt(); Thread.sleep(1000); System.out.println("是否停止1?="+thread.isInterrupted()); System.out.println("是否停止2?="+thread.isInterrupted()); System.out.println("end"); }
結果:
是否停止1?=true
是否停止2?=true
end
從結果可以看出,方法isInterrrupted()並未清除狀態,所以結果為兩個true。
例3:在沉睡中停止
當執行緒呼叫sleep()方法後再呼叫interrupt()方法後會有什麼結果:
class Mythread extends Thread{ @Override public void run() { try { System.out.println("run begin"); Thread.sleep(200000); System.out.println("run end"); } catch (InterruptedException e) { System.out.println("在沉睡中被停止,進入catch!"+this.isInterrupted()); e.printStackTrace(); } } } public class Test01 { public static void main(String[] args) throws InterruptedException { try { Mythread thread=new Mythread(); thread.start(); Thread.sleep(200); thread.interrupt(); }catch(Exception e) { System.out.println("main catch"); e.printStackTrace(); } System.out.println("end!"); } }
6、暫停執行緒:
暫停執行緒意味著此執行緒還可以恢復執行。在java多執行緒中,可以使用suspend()方法暫停執行緒,使用resume()方法恢復執行緒的執行
例1:
class Mythread extends Thread{ private long i=0; public long getI() { return i; } public void setI(long i) { this.i = i; } @Override public void run() { while(true) { i++; } } } public class Test01 { public static void main(String[] args) throws InterruptedException { Mythread thread=new Mythread(); thread.start(); Thread.sleep(5000); //A段 thread.suspend(); System.out.println("A= "+System.currentTimeMillis()+" i="+thread.getI()); Thread.sleep(5000); System.out.println("A= "+System.currentTimeMillis()+" i="+thread.getI()); //B段 thread.resume(); Thread.sleep(5000); //C段 thread.suspend(); System.out.println("B= "+System.currentTimeMillis()+" i="+thread.getI()); Thread.sleep(5000); System.out.println("B= "+System.currentTimeMillis()+" i="+thread.getI()); } }
結果:
從控制檯列印的時間上來看,執行緒的確被暫停了,而且還可以恢復成執行狀態。
7、yield方法:
介紹:yield()方法的作用是放棄當前的CPU資源,將它讓給其他的任務去佔用CPU執行時間。但放棄的時間不確定,有可能剛剛放棄,馬上又獲得CPU時間片
示例:
class Mythread extends Thread{ @Override public void run() { long beginTime=System.currentTimeMillis(); int count=0; for(int i=0;i<500000;i++) {
//Thread.yield();① count=count+(i+1); } long endTime=System.currentTimeMillis(); System.out.println("用時:"+(endTime-beginTime)+"毫秒!"); } } public class Test01 { public static void main(String[] args) throws InterruptedException { Mythread thread=new Mythread(); thread.start(); } }
結果:用時:2毫秒!
現在將①處的程式碼取消註釋,我們再來看一下執行結果:
用時:213毫秒!
將CPU讓給其他資源導致速度變慢
8、執行緒優先順序:
介紹:
在作業系統中,執行緒可以劃分優先順序,優先順序較高的執行緒得到的CPU資源較多,也就是CPU優先執行優先順序較高的執行緒物件中的任務。
設定執行緒優先順序有助於幫“執行緒規劃器”確定在下一次選擇哪一個執行緒來優先執行。
設定執行緒的優先順序使用setPriority()方法,此方法在JDK的原始碼如下:
public final void setPriority(int newPriority) { ThreadGroup g; checkAccess(); if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } if((g = getThreadGroup()) != null) { if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority(); } setPriority0(priority = newPriority); } }
在Java中,執行緒的優先順序為1-10這10個等級,如果小於1或大於10,則JDK丟擲異常throw new IllegalArgumentException()。
通常高優先順序的執行緒總是先執行完,但是並不是一定的,高優先順序和低優先順序的執行緒會交替進行,高優先順序執行的次數多一些
執行緒優先順序的繼承特性:
在Java中,執行緒的優先順序具有繼承性,比如A執行緒啟動B執行緒,則B執行緒的優先順序與A是一樣的。
class Mythread2 extends Thread{ @Override public void run() { System.out.println("Mythread2 run priority="+this.getPriority()); } } class Mythread1 extends Thread{ @Override public void run() { System.out.println("Mythread run priority="+this.getPriority()); Mythread2 thread2=new Mythread2(); thread2.start(); } } public class Test01 { public static void main(String[] args) throws InterruptedException { System.out.println("main thread begin priority="+Thread.currentThread().getPriority()); //Thread.currentThread().setPriority(6);① System.out.println("main thread end priority="+Thread.currentThread().getPriority()); Mythread1 thread1=new Mythread1(); thread1.start(); } } 結果: main thread begin priority=5 main thread end priority=5 Mythread run priority=5 Mythread2 run priority=5
可以看到上面幾個執行緒的優先順序都為5
現在將①處的程式碼註釋掉後的結果是:
main thread begin priority=5 main thread end priority=6 Mythread run priority=6 Mythread2 run priority=6
優先順序被更改後再繼續繼承
9、守護執行緒:
在java中有兩種執行緒,一種是使用者執行緒,另一種是守護執行緒。
守護執行緒是一種特殊的執行緒,它的特性有“陪伴”的含義,當程序中不存在非守護執行緒了,則守護執行緒自動銷燬。典型的守護執行緒就是垃圾回收執行緒,當程序中沒有非守護執行緒了,則垃圾回收執行緒也就沒有存在的必要了,自動銷燬。用個比較通俗的比喻來解釋一下:“守護執行緒”:任何一個守護執行緒都是整個JVM中所有非守護執行緒的“保姆”,只要當前JVM例項中存在任何一個非守護執行緒沒有結束,守護執行緒就在工作,只有當最後一個非守護執行緒結束時,守護執行緒才隨著JVM一同結束工作。Daemon的作用是為其他執行緒的執行提供便利服務,守護執行緒最典型的應用就是GC(垃圾回收器),它就是一個很稱職的守護者。
class Mythread extends Thread{ private int i=0; @Override public void run() { try { while(true) { i++; System.out.println("i="+(i)); Thread.sleep(1000); } }catch(Exception e) { e.printStackTrace(); } } } public class Test01 { public static void main(String[] args) throws InterruptedException { try { Mythread thread=new Mythread(); thread.setDaemon(true); thread.start(); Thread.sleep(5000); System.out.println("我離開Thread物件就不再列印了,也就是停止了"); }catch(Exception e) { e.printStackTrace(); } } }
&n