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
文件)注:若有轉載,請標明原處。如若有錯,也歡迎小夥伴前來指正。