【Java并發(fā)學(xué)習(xí)】之詳解線程的點(diǎn)滴(2)
前言
在前面一個小節(jié)中,我們學(xué)習(xí)了線程的屬性如,ID、名字、優(yōu)先級、狀態(tài)的獲取以及設(shè)置(如果可以操作)以及守護(hù)線程的概念和將線程設(shè)置為守護(hù)線程的方法,接下來這個小節(jié),我們來學(xué)習(xí)多線程中的異常處理以及停止一個線程常用方法
異常處理
異常處理是一個非常重要的內(nèi)容,當(dāng)系統(tǒng)出現(xiàn)可能出現(xiàn)某些問題的時候,我們可以借助異常機(jī)制這個強(qiáng)有力的工具來輔助,從而可以在可能出現(xiàn)問題的時候,決定是否停止運(yùn)行并且給予用戶合理的提示或者忽略該問題,而不是直接就停止程序的運(yùn)行
由于多線程的異步性特點(diǎn),在多線程中處理異常與非多線程中有一些不同。在非多線程的編碼過程中,我們會根據(jù)異常出現(xiàn)的情況進(jìn)行處理,有的是直接處理即可,而有的異常,我們則會將其拋出,交由上層進(jìn)行處理。而由于線程的異步性,是無法將在子線程出現(xiàn)的異常傳遞至父線程中,換句話說,子線程中出現(xiàn)的異常只能在子線程中進(jìn)行處理,而不能將其交給父線程進(jìn)行統(tǒng)一處理,所以,在多線程中出現(xiàn)的異常,有其獨(dú)特的處理方式
// 檢查性異常
class ExceptionTask implements Runnable{
@Override
public void run() {
File file = new File("c:/data.txt");
try {
BufferedReader reader =
new BufferedReader(
new FileReader(file));
} catch (FileNotFoundException e) {
System.out.println("file not found ");
return;
}
System.out.println("Thread finished");
}
}
// 非檢查性異常
class ExceptionTask implements Runnable{
@Override
public void run() {
// 可能會出現(xiàn)異常
try {
int result = Integer.parseInt("123F");
System.out.println("Result is " + result);
}catch (NumberFormatException e){
System.out.println("not a number");
}
}
}
對于非檢查性異常,Java中還提供了另外一種處理方式
// 實(shí)現(xiàn)Thread.UncaughtExceptionHandler,定制對應(yīng)的異常處理器
class ExceptionHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
if (e instanceof NumberFormatException){
System.out.println("could not format number ");
}
}
}
public void test(){
Thread thread = new Thread(exceptionTask);
// 設(shè)置線程的異常處理器,需要注意的是,必須在線程啟動之前進(jìn)行設(shè)置
thread.setUncaughtExceptionHandler(new ExceptionHandler());
thread.start();
}
上面的操作方式是為某一特定的線程設(shè)置非檢查性異常處理器,除此之外,還可以為所有的線程設(shè)置默認(rèn)的異常處理器
// 為所有線程設(shè)置默認(rèn)的異常處理器
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
這三者的順序分別為,try-catch、某一線程的異常處理器、線程的默認(rèn)異常處理器,當(dāng)出現(xiàn)異常時,JVM會堅持按照這個順序堅持對應(yīng)的異常處理器,如果找到合適的處理器,則將對應(yīng)的異常交給它
停止線程
在某些情況下,除了線程執(zhí)行完畢,正常結(jié)束外,我們還可能在線程執(zhí)行過程中中斷/結(jié)束該線程,根據(jù)線程的狀態(tài)不同,結(jié)束某個線程也有不同的方式
線程中有一個特殊的狀態(tài)值,如果某個線程執(zhí)行線程的中斷操作,則將該值設(shè)置為true,利用這個機(jī)制,可以用于判斷是否應(yīng)該讓線程停止
public class InterruptedTest {
public static void main(String[] args) {
Runnable interruptedTask = new InterruptedTask();
Thread thread = new Thread(interruptedTask);
thread.start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt(); // 中斷線程
}
}
class InterruptedTask implements Runnable{
@Override
public void run() {
while (true){
System.out.println("hello world");
// 檢測是否已經(jīng)中斷,如果中斷,則退出循環(huán)
if (Thread.currentThread().isInterrupted()){
break;
}
}
System.out.println("finish");
}
}
需要注意的是,在Thread中,還提供了一個靜態(tài)的interrupted()
方法,該方法同樣可以用于檢查線程的中斷狀態(tài)是否已經(jīng)被設(shè)置,但是該方法會清除線程的中斷狀態(tài),也就是執(zhí)行該方法后,如果線程的中斷狀態(tài)為true,則將其設(shè)置為false
上面中斷線程的方法是一種比較好的方法,但是有一個缺點(diǎn),由于需要進(jìn)行狀態(tài)的檢測,所以只有當(dāng)線程處于運(yùn)行狀態(tài)時,該狀態(tài)值的改變才能被檢測到,換句話說,如果此時線程處于BLOCKED
、WAITING
時,是無法中斷線程的
所以,還需要有另外一種中斷線程的方式,異常,當(dāng)JVM檢測到對應(yīng)的狀態(tài)位改變,并且該線程此時處于上述狀態(tài)時,則利用異常機(jī)制,將中斷的信息發(fā)送給對應(yīng)的線程,這也正是我們在之前使用如,wait()
、sleep()
等方法的時候,需要捕獲一個名為InterruptedException
的異常了
class InterruptedTask implements Runnable{
@Override
public void run() {
synchronized (this){
try {
wait(); // 等待
// TimeUnit.SECOND.sleep(1000);
} catch (InterruptedException e) {
System.out.println("wait up notify");
}
}
System.out.println("status: " + Thread.interrupted());
System.out.println("finish");
}
}
當(dāng)在進(jìn)行遞歸操作的時候,如果想要中斷線程,則需要層層操作,則采用異常機(jī)制,則可以在檢測到中斷信號之后,直接拋出異常,退出線程
// 通過異常的方式來結(jié)束遞歸
class InterruptedTask implements Runnable{
@Override
public void run() {
try {
recursive(0);
} catch (InterruptedException e) {
System.out.println("finish");
}
System.out.println("status: " + Thread.interrupted());
}
void recursive(int cnt) throws InterruptedException {
System.out.println(cnt);
if (Thread.currentThread().isInterrupted()){
throw new InterruptedException();
}
TimeUnit.MICROSECONDS.sleep(100);
recursive(cnt + 1);
}
}
需要注意的是,并非所有的阻塞都是可中斷的,有一些阻塞,如Socket等的是不可中斷的,這也就意味著,即使使用異常機(jī)制,也無法中斷這種類型的阻塞
總結(jié)
本小節(jié)我們主要學(xué)習(xí)了多線程中的異常處理,包括檢查性異常的處理以及非檢查性異常的處理,然后學(xué)習(xí)了中斷線程的方式,包括檢測中斷位,以及異常機(jī)制,其中檢測中斷位的操作在線程被掛起或者阻塞的時候是無法進(jìn)行操作的,所以當(dāng)這種情況發(fā)生時,可以采用拋異常的方式來結(jié)束運(yùn)行