Java執行緒的啟動與中止
- 2020 年 5 月 26 日
- 筆記
- JAVA, java執行緒與並發
一、執行緒與進程的關係
關於進程與執行緒,百度百科上是這樣描述的:
進程(Process) 是電腦中的程式關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是作業系統結構的基礎。 在當代面向執行緒設計的電腦結構中,進程是執行緒的容器。程式是指令、數據及其組織形式的描述,進程是程式的實體。是電腦中的程式關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是作業系統結構的基礎。程式是指令、數據及其組織形式的描述,進程是程式的實體。
執行緒(thread) 是作業系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條執行緒指的是進程中一個單一順序的控制流,一個進程中可以並發多個執行緒,每條執行緒並行執行不同的任務。
簡單的總結一下,就變成了下面的結果:
進程(Process): 程式運行資源分配的最小單位,進程內部有多個執行緒,會共享這個進程的資源。
執行緒(thread) : CPU調度的最小單位,必須依賴進程而存在。
也就是說 執行緒與進程的關係,就像玄幻小說中蟲族的母體和子體一樣,子體離開了母體便不能存活。執行緒亦是如此,必須依賴於進程而存在。
二、Java執行緒的啟動方式
關於Java的執行緒,我們首當其衝的會想到java.lang.Thread
類,這是一種最簡單的創建執行緒的方式,我們只需要通過繼承Thread
類就可以實現:
/**
* @author cai
* 通過繼承Thread類的方式
*/
private static class UserThread extends Thread{
/**
* 重寫 Thread 類的 run() 方法
*/
@Override
public void run() {
System.out.println("this is a thread for extends Thread");
}
}
這裡,我們重寫了Thread
類中的run()
方法,並列印一條語句來表示執行緒的啟動方式,現在我們來測試一下:
public static void main(String[] args) {
// 繼承Thread類的方式
Thread thread = new UserThread();
thread.start();
}
控制台的列印結果:
this is a thread for extends Thread
從列印結果可以看出,我們的執行緒正常的啟動,中間沒有出現意外。除了這種方式之外,JDK
內部又給我們提供了一個介面類:java.lang.Runnable
,同樣,我們也可以通過實現該介面,重寫run()
方法來啟動一個新的執行緒:
/**
* @author cai
* 通過實現Runnable介面的方式
*/
private static class UserRunnable implements Runnable{
/**
* 重寫 Runnable 類的 run() 方法
*/
@Override
public void run() {
System.out.println("this is a thread for implements Runnable");
}
}
這裡我們同樣列印一句話來表示此執行緒的啟動方式,現在來測試一下:
public static void main(String[] args) throws {
// 實現Runnable介面的方式
// 這裡注意:所有執行緒的啟動,都必須通過調用Thread類中start()方法來實現
Runnable runnable = new UserRunnable();
new Thread(runnable).start();
}
相信上面的程式碼,小夥伴們都能看懂(多注意一下第二行的注釋,這是重點),現在來看看控制台的列印結果:
this is a thread for implements Runnable
同理,這裡的執行緒也正常的啟動了。但看到這裡,小夥伴們可能就會產生疑問,為什麼JDK
要多此一舉,提供了一種Thread
類後,為什麼還要提供Runnable
介面類呢?
在這裡給有這個疑惑的小夥伴們答疑下:
因為Java是單繼承,只能繼承一個類,不能繼承多個類。而在我們某些實際的業務中,我們可能需要繼承一個類來處理邏輯業務,那麼就不能再繼承Thread類來處理執行緒相關的操作了。所以
JDK
又為我們提供了一個實現Runnable介面的方式,而且在Java中,一個類是可以實現多個介面的,這樣我們在使用第二種方式處理執行緒就不會有顧忌了。(這裡比較有意思的是:Thread類實現了Runnable介面,有興趣的小夥伴可以去看一下源碼。)
那麼除了上面的兩種方式之外,Java是否提供了第三種方式呢?答案是肯定的,從JDK1.5
開始,JDK
為我們提供了一個新的介面類:java.util.concurrent.Callable
,我們可以通過實現這個介面來啟動一個新得執行緒,而這種方式與實現Runnable
介面來啟動執行緒所不同的是,它會帶有一個返回值,我們來看一下程式碼:
/**
* @author cai
* 通過實現Callable介面的方式
* 帶返回值
*/
private static class UserCallable implements Callable<String>{
/**
* 重寫 Callable 類的 run() 方法
* @return
* @throws Exception
*/
@Override
public String call() throws Exception {
return "this is a thread for implements Callable(return String).";
}
}
測試一下:
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 實現Callable介面的方式 帶返回值
UserCallable callable = new UserCallable();
FutureTask<String> future = new FutureTask<String>(callable);
new Thread(future).start();
System.out.println(future.get());
}
我們這裡將返回值列印一下:
this is a thread for implements Callable(return String).
可以看出,我們的執行緒正常的啟動,沒有問題。
那麼看了以上三種Java執行緒的啟動方式,相信肯定有很多小夥伴會好奇,如果我要中止一個執行緒,我需要怎麼做呢?讓我們來一起看看吧。
三、Java執行緒的中止
怎樣讓一個正在運行的執行緒安全的停止工作呢?這裡有兩種方法:
1、執行緒自然的終止:程式正常的執行完或者拋出未處理的異常。
程式正常的執行完就不必再說,以上的程式碼都屬於此類,我們來看一看拋出未處理異常的情況,這裡我們將上述實現Runnable
介面來啟動執行緒的程式碼修改一下:
/**
* @author cai
* 通過實現Runnable介面的方式
*/
private static class UserRunnable implements Runnable{
/**
* 重寫 Runnable 類的 run() 方法
*/
@Override
public void run() {
// 重點加了這樣的一行程式碼
int i = 10 / 0;
System.out.println("this is a thread for implements Runnable");
}
}
這裡我們加了一行程式碼,小夥伴們應該都可以看懂,這行程式碼是必定會拋出異常的,但我們又沒有對異常進行處理,現在來看一下控制台的列印結果:
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
at com.cai.thread.create.NewThread$UserRunnable.run(NewThread.java:39)
at java.lang.Thread.run(Thread.java:748)
從結果可以看出,程式運行到了int i = 10 / 0
這裡就停止了,並沒有列印出下一行的結果,由此可見,執行緒到了這裡便停止了,沒有再走下去。
2、調用JDK
為我們提供的一系列方法
stop(),resume(),suspend(),interrupt(),isInterrupted(),interrupted()
這些方法都是JDK
為我們提供的,但是“stop(),resume(),suspend()`已經不建議使用了,原因如下:
stop()
: 會導致執行緒不會正常的釋放資源,「惡意」的中斷當前正在運行的執行緒,不管執行緒的邏輯是否完整。
suspend()
與resume()
: 極易造成公共的同步對象的獨佔,使得其他執行緒無法訪問公共同步對象,產生死鎖。(這個例子以後在講執行緒鎖的時候會解釋。)
我們先來看一下調用stop()
的例子:
/**
* @author cai
* 調用 stop() 方法的例子
*/
private static class UserRunnable implements Runnable{
@Override
public void run() {
try {
// 讓此執行緒休眠1秒鐘
Thread.sleep(1000);
}catch (Exception e){
// 異常 捕獲處理
}
// 此處不會運行,控制台不會列印
// 若實際開發中,這裡要處理一個很重要的業務邏輯,那麼這裡就會有很大的問題。
// 所以不建議使用
System.out.println("程式碼到此處不會運行");
}
}
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new UserRunnable();
Thread thread = new Thread(runnable);
thread.start();
// 強行中止執行緒
// 從這裡可以看出,JDK已經不建議使用stop()方法了,添加了@Deprecated註解
thread.stop();
}
我這裡將測試程式碼也一併貼了上去,可以在程式碼的注釋中得到相關的結果。講完這些,我們來看看剩下的三個方法:interrupt(),isInterrupted(),interrupted()
interrupt()
:調用此方法會中斷一個執行緒,但並不是強行關閉這個執行緒,只是先給這個執行緒打個招呼,同事將執行緒的中斷標誌位設置為true
,執行緒是否中斷,由執行緒本身決定。
isInterrupted()
:判斷當前的執行緒是否處於中斷狀態(返回true
或者false
)。
interrupted()
:靜態方法,判斷當前的執行緒是否處於中斷狀態,同時將中斷的標誌位設置為false
。注意:如果方法中拋出了
InterruptedException
異常,那麼執行緒的中斷標誌位會被複位成false
,如果確實需要中斷執行緒的操作,我們需要在catch
語句中再次調用interrupt()
方法。
解釋完了,直接上程式碼吧:
/**
* @author cai
* 調用 interrupt(),isInterrupted(),interrupted() 方法的例子
*/
private static class UserThread extends Thread{
// 給執行緒一個名字,創建對象時賦值
public UserThread(String threadName) {
super(threadName);
}
@Override
public void run() {
// 獲得執行緒的名字
String threadName = Thread.currentThread().getName();
try {
// @2
System.out.println(threadName+" flag is " + isInterrupted());
// 休眠一秒鐘 需要捕獲異常 InterruptedException @3
Thread.sleep(1000);
}catch (InterruptedException e){
// 列印一下 isInterrupted() 的狀態 @4
System.out.println(threadName+" catch interrput flag is " + isInterrupted());
// 調用 interrupt() 方法 中斷執行緒操作 @5
interrupt();
}
// 列印執行緒的名字 @6
System.out.println(threadName+" interrput flag is " + interrupted());
// @7
System.out.println(threadName+" interrput flag is " + isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
// interrupt(),isInterrupted(),interrupted() 演示
Thread thread = new UserThread("caiThread");
thread.start();
// @1
thread.interrupt();
}
這裡為了方便解釋,我分了步驟:
1、@1 的位置調用的
interrupt()
方法,所以這裡的標誌位是true
,所以 @2 的位置列印結果為true
。2、@3 位置的
sleep
方法會拋出InterruptedException
異常,我這裡捕獲了,在看之前的理論,拋出此異常,標誌位會重置為false
,所以@4 這裡的列印結果為false
。3、@5 位置再次調用了
interrupt()
,又把標誌位改為了false
,所以 @6 這裡列印的結果為true
,但是這裡注意的是,@6 調用了interrupted()
這個靜態方法,所以標誌位又變為了false
,所以@7 列印的結果為false
。
控制台列印結果:
caiThread catch flag is true
caiThread catch interrput flag is false
caiThread interrput flag is true
caiThread interrput flag is false
小夥伴們可以通過對比結果、程式碼和解釋一起看,相信還是很容易明白的。
對執行緒的了解再多一點點
Java執行緒總歸下來有五種狀態:
新建、就緒、阻塞、運行、死亡
而這裡對應的方法卻有很多種,具體的關係,我這裡準備了一張圖:
這張圖上面的各種方法我都會在下次的文章中分享,這次的分享就到這裡,希望大家能夠喜歡。
最後
程式碼地址://github.com/caimm123/javaThread (先閱讀
README.md
文件)注:若有轉載,請標明原處。如若有錯,也歡迎小夥伴前來指正。