前言
昨天已經寫了:
如果沒看的同學建議先去閱讀一遍哦~
在寫文章之前通讀了一遍《Java 核心技術 卷一》的併發章節和《Java併發程式設計實戰》前面的部分,回顧了一下以前寫過的筆記。從今天開始進入多執行緒的知識點咯~
我其實也是相當於從零開始學多執行緒的,如果文章有錯的地方還請大家多多包含,不吝在評論區下指正呢~~
一、Thread執行緒類API
宣告本文使用的是JDK1.8
實現多執行緒從本質上都是由Thread類來進行操作的~我們來看看Thread類一些重要的知識點。Thread這個類很大,不可能整個把它看下來,只能看一些常見的、重要的方法。
頂部註釋的我們已經解析過了,如果不知道的同學可前往:多執行緒三分鐘就可以入個門了!
1.1設定執行緒名
我們在使用多執行緒的時候,想要檢視執行緒名是很簡單的,呼叫Thread.currentThread().getName()
即可。
如果沒有做什麼的設定,我們會發現執行緒的名字是這樣子的:主執行緒叫做main,其他執行緒是Thread-x
下面我就帶著大家來看看它是怎麼命名的:

nextThreadNum()
的方法實現是這樣的:

基於這麼一個變數-->執行緒初始化的數量

點進去看到init方法就可以確定了:

看到這裡,如果我們想要為執行緒起個名字,那也是很簡單的。Thread給我們提供了構造方法!

下面我們來測試一下:
- 實現了Runnable的方式來實現多執行緒:
public class MyThread implements Runnable {
@Override
public void run() {
// 列印出當前執行緒的名字
System.out.println(Thread.currentThread().getName());
}
}
複製程式碼
測試:
public class MyThreadDemo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//帶參構造方法給執行緒起名字
Thread thread1 = new Thread(myThread, "關注公眾號Java3y");
Thread thread2 = new Thread(myThread, "qq群:742919422");
thread1.start();
thread2.start();
// 列印當前執行緒的名字
System.out.println(Thread.currentThread().getName());
}
}
複製程式碼
結果:

當然了,我們還可以通過setName(String name)
的方法來改掉執行緒的名字的。我們來看看方法實現;

檢查是否有許可權修改:

至於threadStatus這個狀態屬性,貌似沒發現他會在哪裡修改:

1.2守護執行緒
守護執行緒是為其他執行緒服務的
- 垃圾回收執行緒就是守護執行緒~
守護執行緒有一個特點:
- 當別的使用者執行緒執行完了,虛擬機器就會退出,守護執行緒也就會被停止掉了。
- 也就是說:守護執行緒作為一個服務執行緒,沒有服務物件就沒有必要繼續執行了
使用執行緒的時候要注意的地方
- 線上程啟動前設定為守護執行緒,方法是
setDaemon(boolean on)
- 使用守護執行緒不要訪問共享資源(資料庫、檔案等),因為它可能會在任何時候就掛掉了。
- 守護執行緒中產生的新執行緒也是守護執行緒
測試一波:
public class MyThreadDemo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//帶參構造方法給執行緒起名字
Thread thread1 = new Thread(myThread, "關注公眾號Java3y");
Thread thread2 = new Thread(myThread, "qq群:742919422");
// 設定為守護執行緒
thread2.setDaemon(true);
thread1.start();
thread2.start();
System.out.println(Thread.currentThread().getName());
}
}
複製程式碼
上面的程式碼執行多次可以出現(電腦效能足夠好的同學可能測試不出來):執行緒1和主執行緒執行完了,我們的守護執行緒就不執行了~

原理:這也就為什麼我們要在啟動之前設定守護執行緒了。

1.3優先順序執行緒
執行緒優先順序高僅僅表示執行緒獲取的CPU時間片的機率高,但這不是一個確定的因素!
執行緒的優先順序是高度依賴於作業系統的,Windows和Linux就有所區別(Linux下優先順序可能就被忽略了)~
可以看到的是,Java提供的優先順序預設是5,最低是1,最高是10:

實現:

setPriority0
是一個本地(navite)的方法:
private native void setPriority0(int newPriority);
複製程式碼
1.4執行緒生命週期
在上一篇介紹的時候其實也提過了執行緒的執行緒有3個基本狀態:執行、就緒、阻塞
在Java中我們就有了這個圖,Thread上很多的方法都是用來切換執行緒的狀態的,這一部分是重點!

其實上面這個圖是不夠完整的,省略掉了一些東西。後面在講解的執行緒狀態的時候我會重新畫一個~
下面就來講解與執行緒生命週期相關的方法~
1.4.1sleep方法
呼叫sleep方法會進入計時等待狀態,等時間到了,進入的是就緒狀態而並非是執行狀態!

於是乎,我們的圖就可以補充成這樣:

1.4.2yield方法
呼叫yield方法會先讓別的執行緒執行,但是不確保真正讓出
- 意思是:我有空,可以的話,讓你們先執行

於是乎,我們的圖就可以補充成這樣:

1.4.3join方法
呼叫join方法,會等待該執行緒執行完畢後才執行別的執行緒~

我們進去看看具體的實現:

wait方法是在Object上定義的,它是native本地方法,所以就看不了了:

wait方法實際上它也是**計時等待(如果帶時間引數)**的一種!,於是我們可以補充我們的圖:

1.4.3interrupt方法
執行緒中斷在之前的版本有stop方法,但是被設定過時了。現在已經沒有強制執行緒終止的方法了!
由於stop方法可以讓一個執行緒A終止掉另一個執行緒B
- 被終止的執行緒B會立即釋放鎖,這可能會讓物件處於不一致的狀態。
- 執行緒A也不知道執行緒B什麼時候能夠被終止掉,萬一執行緒B還處理執行計算階段,執行緒A呼叫stop方法將執行緒B終止,那就很無辜了~
總而言之,Stop方法太暴力了,不安全,所以被設定過時了。
我們一般使用的是interrupt來請求終止執行緒~
- 要注意的是:interrupt不會真正停止一個執行緒,它僅僅是給這個執行緒發了一個訊號告訴它,它應該要結束了(明白這一點非常重要!)
- 也就是說:Java設計者實際上是想執行緒自己來終止,通過上面的訊號,就可以判斷處理什麼業務了。
- 具體到底中斷還是繼續執行,應該由被通知的執行緒自己處理
Thread t1 = new Thread( new Runnable(){
public void run(){
// 若未發生中斷,就正常執行任務
while(!Thread.currentThread.isInterrupted()){
// 正常任務程式碼……
}
// 中斷的處理程式碼……
doSomething();
}
} ).start();
複製程式碼
再次說明:呼叫interrupt()並不是要真正終止掉當前執行緒,僅僅是設定了一箇中斷標誌。這個中斷標誌可以給我們用來判斷什麼時候該幹什麼活!什麼時候中斷由我們自己來決定,這樣就可以安全地終止執行緒了!
我們來看看原始碼是怎麼講的吧:

再來看看剛才說丟擲的異常是什麼東東吧:

所以說:interrupt方法壓根是不會對執行緒的狀態造成影響的,它僅僅設定一個標誌位罷了
interrupt執行緒中斷還有另外兩個方法(檢查該執行緒是否被中斷):
- 靜態方法interrupted()-->會清除中斷標誌位
- 例項方法isInterrupted()-->不會清除中斷標誌位


上面還提到了,如果阻塞執行緒呼叫了interrupt()方法,那麼會丟擲異常,設定標誌位為false,同時該執行緒會退出阻塞的。我們來測試一波:
public class Main {
/**
* @param args
*/
public static void main(String[] args) {
Main main = new Main();
// 建立執行緒並啟動
Thread t = new Thread(main.runnable);
System.out.println("This is main ");
t.start();
try {
// 在 main執行緒睡個3秒鐘
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("In main");
e.printStackTrace();
}
// 設定中斷
t.interrupt();
}
Runnable runnable = () -> {
int i = 0;
try {
while (i < 1000) {
// 睡個半秒鐘我們再執行
Thread.sleep(500);
System.out.println(i++);
}
} catch (InterruptedException e) {
// 判斷該阻塞執行緒是否還在
System.out.println(Thread.currentThread().isAlive());
// 判斷該執行緒的中斷標誌位狀態
System.out.println(Thread.currentThread().isInterrupted());
System.out.println("In Runnable");
e.printStackTrace();
}
};
}
複製程式碼
結果:

接下來我們分析它的執行流程是怎麼樣的:

2018年4月18日20:32:15(哇,這個方法真的消耗了我非常長的時間).....感謝@開始de痕跡的指教~~~~~
該參考資料:
- www.cnblogs.com/w-wfy/p/641…
- www.cnblogs.com/carmanlonel…
- www.zhihu.com/question/41…
- www.zhihu.com/question/41…
二、總結
可以發現我們的圖是還沒有補全的~後續的文章講到同步的時候會繼續使用上面的圖的。在Thread中重要的還是那幾個可以切換執行緒狀態的方法,還有理解中斷的真正含義。
使用執行緒會導致我們資料不安全,甚至程式無法執行的情況的,這些問題都會再後面講解到的~
之前在學習作業系統的時候根據《計算機作業系統-湯小丹》這本書也做了一點點筆記,都是比較淺顯的知識點。或許對大家有幫助
參考資料:
- 《Java核心技術卷一》
- 《Java併發程式設計實戰》
- 《計算機作業系統-湯小丹》
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y。
文章的目錄導航: