一、線程的創(chuàng)建
1.Oracle 官網(wǎng)描述
There are two ways to create a new thread of execution.
One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started.
The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started.
有兩種方式可以創(chuàng)建新的執(zhí)行線程。一種方法是定義一個(gè) Thread 類的子類,該子類重寫 Thread 類的 run 方法。然后可以分配并啟動子類的實(shí)例。創(chuàng)建線程的另一種方法是定義一個(gè)實(shí)現(xiàn) Runnable 接口的類,然后可以分配該類的實(shí)例,在創(chuàng)建 Thread 時(shí)將其作為參數(shù)傳遞并啟動。
1.1 繼承 Thread 類
public class ThreadStyle extends Thread {
@Override
public void run() {
System.out.println("用 Thread 方式創(chuàng)建線程");
}
public static void main(String[] args) {
new ThreadStyle().start();
}
}
定義一個(gè)類繼承 Thread 類,然后重寫 run 方法,直接通過該類的實(shí)例啟動線程,輸出結(jié)果為:用 Thread 方式創(chuàng)建線程。
1.2 實(shí)現(xiàn) Runnable 接口
public class RunnableStyle implements Runnable {
@Override
public void run() {
System.out.println("用 Runnable 方式實(shí)現(xiàn)線程");
}
public static void main(String[] args) {
new Thread(new RunnableStyle()).start();
}
}
定義一個(gè)類實(shí)現(xiàn) Runnable 接口并重寫 run 方法,通過將該 Runnable 實(shí)例傳入 Thread 類參數(shù)中啟動線程,輸出結(jié)果為:用 Runnable 方式實(shí)現(xiàn)線程。
1.3 兩種方法對比
準(zhǔn)確的講,創(chuàng)建線程都是通過構(gòu)造 Thread 類這一種方式實(shí)現(xiàn),實(shí)現(xiàn)線程的執(zhí)行單元有兩種方式。
public class Thread implements Runnable {
/** 省略 */
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
- 方法一(繼承 Thread 類):Thread 類本身也是實(shí)現(xiàn)了 Runnable 接口然后重寫 run 方法的,當(dāng)通過繼承 Thread 類創(chuàng)建線程時(shí),Thread 類的整個(gè) run 方法都會被重寫。
- 方法二(實(shí)現(xiàn) Runnable 接口):Thread 類中有一個(gè)名為 target 的 Runnable 變量,在 Thread(Runnable target) 構(gòu)造方法中傳入 Runnable 實(shí)例會初始化 target 屬性,通過該方法創(chuàng)建線程,最終只會調(diào)用 target.run() 方法,并不會重寫整個(gè) run 方法。
通過方法二創(chuàng)建線程的方式其實(shí)更好:
- 實(shí)現(xiàn) Runnable 接口的方式與 Thread 類解耦。
- 接口可以多實(shí)現(xiàn),繼承 Thread 類的方式限制了可擴(kuò)展性。
- 繼承 Thread 類的話,每次新建線程都會去創(chuàng)建一個(gè)獨(dú)立的線程,開銷大,不適合資源共享。實(shí)現(xiàn) Runnable 接口的話,則很容易的實(shí)現(xiàn)資源共享,而且可以利用線程池等工具,大大減小創(chuàng)建線程和銷毀線程帶來的損耗。
同時(shí)使用這兩種方法創(chuàng)建線程:
先通過在 Thread 類構(gòu)造器中傳入匿名內(nèi)部類(Runnable 實(shí)例)的方式創(chuàng)建線程,然后在此基礎(chǔ)上重寫了 Thread 類的 run 方法,最終輸出:使用 Thread 方式創(chuàng)建。因?yàn)閭魅?Runnable 實(shí)例創(chuàng)建線程是調(diào)用 run 方法中的 target.run() 執(zhí)行的,但是后面重寫了 run 方法,導(dǎo)致此方法失效。
public class BothRunnableAndThread {
public static void main(String[] args) {
new Thread(() -> System.out.println("使用 Runnable 方式創(chuàng)建")) {
// 重寫了 run 方法,覆蓋了 run 里的三行代碼
// runnable 傳入進(jìn)去卻沒有運(yùn)行
@Override
public void run() {
System.out.println("使用 Thread 方式創(chuàng)建");
}
}.start();
}
}
創(chuàng)建線程的方式通常分為兩類,除此之外,通過實(shí)現(xiàn) Callable 接口、使用線程池等方式也可以創(chuàng)建線程,但是本質(zhì)上都是繼承 Thread 類或?qū)崿F(xiàn) Runnable 接口這兩種方法。
1.4 線程初始化
不管通過哪種方式創(chuàng)建線程,都會調(diào)用線程初始化函數(shù) init,可以通過不同的構(gòu)造函數(shù)初始化線程的部分參數(shù)。如下 init 函數(shù)所示,一個(gè)新構(gòu)造的線程對象是由其父線程來進(jìn)行空間分配的,而子線程繼承了父線程的 daemon、priority 等屬性,同時(shí)還會分配一個(gè)唯一的 id 來標(biāo)識這個(gè)線程。
public class Thread implements Runnable {
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
// 省略部分代碼
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 當(dāng)前線程就是該線程的父線程
Thread parent = currentThread();
// 復(fù)制父線程的 daemon 和 priority 屬性
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
this.target = target;
setPriority(priority);
// 復(fù)制父線程的 inheritableThreadLocals
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// 分配線程 ID
tid = nextThreadID();
}
}
2.實(shí)現(xiàn) Callable 接口
public class CallableDemo implements Callable<String> {
@Override
public String call() {
return "hncboy";
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<String> ft = new FutureTask<>(new CallableStyle());
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
}
3.使用線程池
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(new TaskThread());
}
private static class TaskThread implements Runnable {
@Override
public void run() {
System.out.println("hncboy");
}
}
}
4.使用定時(shí)器
public class TimerTaskDemo {
public static void main(String[] args) {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println("hncboy");
}
}, 1000, 1000);
}
}
二、線程的啟動
1.start() 方法
1.1 方法含義
啟動新線程:通知 JVM 在有空閑的情況下啟動線程,本質(zhì)是請求 JVM 來運(yùn)行我們的線程,線程何時(shí)運(yùn)行由線程調(diào)度器來確定。該線程啟動的同時(shí)會啟動兩個(gè)線程:第一個(gè)是用來執(zhí)行 start 方法的父線程或主線程,第二個(gè)是被創(chuàng)建的子線程。
準(zhǔn)備工作:讓線程處于就緒狀態(tài)(已經(jīng)獲得了除 CPU 以外的其他資源,如已經(jīng)設(shè)置了上下文,線程狀態(tài),棧等),做完準(zhǔn)備工作后,才能被 JVM 或操作系統(tǒng)調(diào)度到執(zhí)行狀態(tài)獲取 CPU 資源,然后才會執(zhí)行 run 方法。
重復(fù)調(diào)用 start() :拋出異常 Exception in thread "main" java.lang.IllegalThreadStateException。一旦線程 start 以后,就從 NEW 狀態(tài)進(jìn)入到其他狀態(tài),比如 RUNNABLE,只有處于 NEW 狀態(tài)的線程才能調(diào)用 start() 方法。
1.2 原理分析
通過 threadStatus 屬性來判斷是否重復(fù)啟動并拋出異常,實(shí)際的啟動方法是 native 方法 start0()。
public class Thread implements Runnable {
/**
* 線程狀態(tài),初始化為 0,表示還未啟
*/
private volatile int threadStatus = 0;
public synchronized void start() {
// 判斷線程的狀態(tài),也就是判斷是否啟動,重復(fù)啟動時(shí)拋出 IllegalThreadStateException
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 將線程加入線程組
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
// 告知線程組該線程啟動失敗
group.threadStartFailed(this);
}
} catch (Throwable ignore) {}
}
}
private native void start0();
}
通過 /src/share/native/java/lang/Thread.c 可知,start0() 方法對應(yīng) JVM_StartThread 方法
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread},
};
位于 /src/hotspot/share/prims/jvm.cpp 的 JVM_StartThread 方法中有段注釋
// Since JDK 5 the java.lang.Thread threadStatus is used to prevent
// re-starting an already started thread, so we should usually find
// that the JavaThread is null. However for a JNI attached thread
// there is a small window between the Thread object being created
// (with its JavaThread set) and the update to its threadStatus, so we
// have to check for this
該段注釋說自從 JDK5 后 使用 Thread 類的 threadStatus 屬性去方式線程重復(fù)啟動,接下來看下 /src/share/vm/runtime/thread.cpp 中的 start 方法,該方法中判斷如果該線程是 Java 線程,則將該線程的狀態(tài)改為 RUNNABLE。
void Thread::start(Thread* thread) {
trace("start", thread);
if (!DisableStartThread) {
if (thread->is_Java_thread()) {
// 這里調(diào)用 set_thread_status 方法將線程的狀態(tài)修改為 RUNNALBE
java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
java_lang_Thread::RUNNABLE);
}
os::start_thread(thread);
}
}
2.run() 方法
run() 只是 Thread 類的一個(gè)基本方法
public class Thread implements Runnable {
/** 省略 */
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
3.比較兩方法
輸出:main 和 Thread-0
public class StartAndRunMethod {
public static void main(String[] args) {
Runnable runnable = () -> System.out.println(Thread.currentThread().getName());
runnable.run();
new Thread(runnable).start();
}
}
調(diào)用 start 方法才是真正意義上啟動了一個(gè)線程,會經(jīng)歷線程的各個(gè)生命周期,如果直接調(diào)用 run 方法,則只是普通的調(diào)用方法,不會通過子線程去調(diào)用。
三、線程的終止
1.過期的 suspend()、resume()、stop()
這三個(gè)方法已經(jīng)被廢除,通過查看 Oracle 官方文檔 可以得知。使用 stop() 方法停止線程會釋放線程的所有 monitor,該方法在終止一個(gè)線程時(shí)不會保證線程的資源正常釋放,并且拋出 ThreadDeath 異常,通常是沒有給予線程完成資源釋放工作的機(jī)會,因此會導(dǎo)致程序出現(xiàn)數(shù)據(jù)不同步。suspend() 方法則容易造成死鎖,該方法在調(diào)用后,線程不會釋放已經(jīng)占有的資源(比如鎖),而是占有著資源進(jìn)入掛起狀態(tài)。resume() 必須和 suspend() 一起使用,當(dāng)要恢復(fù)目標(biāo)線程的線程在調(diào)用 resume 之前嘗試鎖定這個(gè) monitor,此時(shí)就會導(dǎo)致死鎖。
2.volatile 標(biāo)志位
通過 volatile 修飾的共享變量可以進(jìn)行線程的終止。
2.1 成功案例
子線程每隔 1 秒輸出:持續(xù)運(yùn)行。主線程在 2 秒后將 stop 置為 true,此時(shí)子線程 while 循環(huán)停止,子線程運(yùn)行結(jié)束。循環(huán)只進(jìn)行了兩次。
public class RightVolatileDemo {
private static volatile boolean stop = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (!stop) {
System.out.println("持續(xù)運(yùn)行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
TimeUnit.SECONDS.sleep(2);
stop = true;
}
}
運(yùn)行結(jié)果如下:
持續(xù)運(yùn)行
持續(xù)運(yùn)行
2.3 失敗案例
使用 volatile 的局限性,當(dāng)線程陷入阻塞時(shí),使用 volatile 修飾的變量無法停止線程。
通過生產(chǎn)者消費(fèi)者例子來演示阻塞情況下 volatile 的局限性,定義一個(gè)生產(chǎn)者類實(shí)現(xiàn) Runnable 接口重寫 run 方法,在 run 中當(dāng) volatile 修飾的 canceled 變量為 false 時(shí),生產(chǎn)者通過 BlockingQueue 的 put 方法不斷添加數(shù)據(jù),當(dāng)阻塞隊(duì)列到達(dá)上限時(shí),put 方法會阻塞。定義一個(gè)消費(fèi)者類,通過 needMoreCount 方法判斷消費(fèi)者是否結(jié)束消費(fèi)。
在主函數(shù)中初始化一個(gè)長度為 10 的阻塞隊(duì)列,構(gòu)建生產(chǎn)者和消費(fèi)者實(shí)例,當(dāng)消費(fèi)者結(jié)束消費(fèi)時(shí),將生產(chǎn)者的 canceled 屬性值改為 true,但是此時(shí)生產(chǎn)者仍然在運(yùn)行,因?yàn)樯a(chǎn)者線程阻塞在 put 方法。這就是 volatile 標(biāo)志位的局限性了。
public class WrongVolatileDemo {
public static void main(String[] args) throws InterruptedException {
// 定義容量為 10 的阻塞隊(duì)列
BlockingQueue<Integer> storage = new ArrayBlockingQueue<>(10);
// 啟動生產(chǎn)者線程
Thread producerThread = new Thread(new Producer(storage));
producerThread.start();
Thread.sleep(1000);
// 啟動消費(fèi)者
Consumer consumer = new Consumer(storage);
while (consumer.needMoreCount()) {
System.out.println("消費(fèi)者消費(fèi):" + consumer.getStorage().take());
Thread.sleep(100);
}
System.out.println("消費(fèi)者消費(fèi)完全結(jié)束");
// 此時(shí)生產(chǎn)者不應(yīng)該繼續(xù)生產(chǎn)
Producer.canceled = true;
}
/**
* 生產(chǎn)者
*/
private static class Producer implements Runnable {
static volatile boolean canceled = false;
private BlockingQueue<Integer> storage;
public Producer(BlockingQueue<Integer> storage) {
this.storage = storage;
}
@Override
public void run() {
int count = 1;
try {
while (!canceled) {
// 如果隊(duì)列滿的話,put 方法會阻塞當(dāng)前線程
storage.put(count);
System.out.println("生產(chǎn)者生產(chǎn):" + count);
count++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("生產(chǎn)者停止運(yùn)行");
}
}
}
/**
* 消費(fèi)者
*/
private static class Consumer {
private BlockingQueue<Integer> storage;
public Consumer(BlockingQueue<Integer> storage) {
this.storage = storage;
}
public BlockingQueue<Integer> getStorage() {
return storage;
}
public boolean needMoreCount() {
return Math.random() < 0.95;
}
}
}
3.interrupt 方法
interrupt 翻譯為中斷,中斷可以理解為線程的一個(gè)標(biāo)識位屬性,它表示一個(gè)運(yùn)行中的線程是否被其他線程進(jìn)行了中斷操作。中斷好比其他線程對該線程打了個(gè)招呼,其他線程通過調(diào)用該線程的 interrupt() 方法對其進(jìn)行中斷操作。
舉幾個(gè)例子來演示 interrupt 的不同用法。
3.1 不帶阻塞的中斷
該例子是最簡單的中斷,thread 線程啟動后,休眠 1ms 再調(diào)用該對象的 interrupt 方法,此時(shí)線程中正在執(zhí)行的循環(huán)檢測到 Thread.currentThread().isInterrupted() 為 true 結(jié)束循環(huán),輸出 count 變量的值。
當(dāng)線程調(diào)用自身的 interrupt 方法時(shí),會將中斷標(biāo)記設(shè)置為 ture,線程內(nèi)部循環(huán)會通過檢查自身是否被中斷來結(jié)束循環(huán),而 線程內(nèi)部的 isInterrupted() 方法就能判斷線程是否被中斷。
public class InterruptThreadWithoutSleep {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
int count = 0;
// 檢查自身是否被中斷來結(jié)束循環(huán)
while (!Thread.currentThread().isInterrupted()) {
count++;
}
System.out.println(count);
});
thread.start();
Thread.sleep(1);
// 設(shè)置中斷標(biāo)記
thread.interrupt();
}
}
3.2 帶有阻塞的中斷
該例子演示帶有 sleep 阻塞的中斷方法使用。sleep 方法使用需要拋出 InterruptedException,說明該方法可以響應(yīng) interrupt 中斷。在線程啟動后,該線程會休眠 1s,而主線程在休眠 100ms 后會調(diào)用中斷方法,此時(shí)該線程是處于阻塞狀態(tài),在阻塞狀態(tài)下響應(yīng)到中斷,sleep 方法會拋出 InterruptedException ,但是在拋出該異常前,JVM 會先將該線程的中斷標(biāo)識位清除,然后才拋出 InterruptedException,此時(shí)調(diào)用 isInterrupted() 方法將會返回 false。
如果在執(zhí)行過程中,每次循環(huán)都會調(diào)用 sleep 方法,那么其實(shí)可以不需要每次迭代都通過 isInterrupted() 方法檢查中斷,因?yàn)?sleep 方法會響應(yīng)中斷。
public class InterruptThreadWithSleep {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("中斷標(biāo)記:" + Thread.currentThread().isInterrupted());
}
});
thread.start();
Thread.sleep(100);
thread.interrupt();
}
}
運(yùn)行結(jié)果如下:
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.hncboy.interrupt.InterruptThreadWithSleep.lambda$main$0(InterruptThreadWithSleep.java:15)
at java.lang.Thread.run(Thread.java:748)
中斷標(biāo)記:false
3.3 interrupt 相關(guān)方法
3.4.1 interrupt()
設(shè)置中斷標(biāo)記,最終調(diào)用 native 的 interrupt0() 方法設(shè)置中斷標(biāo)記。
public void interrupt() {
if (this != Thread.currentThread())
// 權(quán)限檢查
checkAccess();
synchronized (blockerLock) {
// IO 讀寫相關(guān)
Interruptible b = blocker;
if (b != null) {
interrupt0();
b.interrupt(this);
return;
}
}
// 該方法一定會執(zhí)行
interrupt0();
}
private native void interrupt0();
找到 interrupt0 方法對應(yīng)的 JVM_Interrupt 方法,找到該方法代碼。
JVM_ENTRY(void, JVM_Interrupt(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_Interrupt");
// Ensure that the C++ Thread and OSThread structures aren't freed before we operate
oop java_thread = JNIHandles::resolve_non_null(jthread);
MutexLockerEx ml(thread->threadObj() == java_thread ? NULL : Threads_lock);
// We need to re-resolve the java_thread, since a GC might have happened during the
// acquire of the lock
JavaThread* thr = java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread));
if (thr != NULL) {
Thread::interrupt(thr);
}
JVM_END
找到關(guān)鍵方法 Thread::interrupt 的代碼。
void Thread::interrupt(Thread* thread) {
trace("interrupt", thread);
debug_only(check_for_dangling_thread_pointer(thread);)
os::interrupt(thread);
}
找到關(guān)鍵方法 os::interrupt 的代碼,此時(shí)找到了設(shè)置中斷標(biāo)記的方法,Java 中的每個(gè)線程都與操作系統(tǒng)的線程一一對應(yīng),一個(gè) osthread 就對應(yīng) Java 中的一個(gè)線程,如果 osthread 沒有被設(shè)置為中斷,則設(shè)置中斷標(biāo)記為 true。
void os::interrupt(Thread* thread) {
assert(Thread::current() == thread || Threads_lock->owned_by_self(),
"possibility of dangling Thread pointer");
OSThread* osthread = thread->osthread();
// 如果線程沒有被中斷
if (!osthread->interrupted()) {
// 設(shè)置中斷標(biāo)記為 true
osthread->set_interrupted(true);
// More than one thread can get here with the same value of osthread,
// resulting in multiple notifications. We do, however, want the store
// to interrupted() to be visible to other threads before we execute unpark().
OrderAccess::fence();
ParkEvent * const slp = thread->_SleepEvent ;
if (slp != NULL) slp->unpark() ;
}
// For JSR166. Unpark even if interrupt status already was set
if (thread->is_Java_thread())
((JavaThread*)thread)->parker()->unpark();
ParkEvent * ev = thread->_ParkEvent ;
if (ev != NULL) ev->unpark() ;
}
3.4.2 isInterrupted() 和 interrupted()
返回線程的中斷狀態(tài)。interrupted 為靜態(tài)方法,兩個(gè)方法都調(diào)用了 isInterrupted 方法,不過傳入的參數(shù)不一樣,true 表示清除中斷狀態(tài),false 表示不清除。
Thread.interrupted() 在哪個(gè)線程里被調(diào)用,就返回哪個(gè)線程的中斷標(biāo)志。
public boolean isInterrupted() {
return isInterrupted(false);
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
private native boolean isInterrupted(boolean ClearInterrupted);
3.4.2 綜合例子
public class InterruptComprehensive {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {});
// 啟動線程
thread.start();
// 設(shè)置中斷標(biāo)志
thread.interrupt();
// 獲取中斷標(biāo)志,被中斷了返回 true
System.out.println("isInterrupted: " + thread.isInterrupted());
// 獲取中斷標(biāo)志并重置,interrupted 靜態(tài)方法調(diào)用的是執(zhí)行它的線程,也就是 main 線程,返回 false
System.out.println("isInterrupted: " + thread.interrupted());
// 獲取中斷標(biāo)志并重置,Main 函數(shù)沒有沒有被中斷,返回 false
System.out.println("isInterrupted: " + Thread.interrupted());
// 獲取中斷標(biāo)志,中斷標(biāo)記沒有被清除,返回 true
System.out.println("isInterrupted: " + thread.isInterrupted());
thread.join();
System.out.println("Main thread is over.");
}
}
運(yùn)行結(jié)果:
isInterrupted: true
isInterrupted: false
isInterrupted: false
isInterrupted: true
Main thread is over.
3.4 能響應(yīng)中斷的部分方法
有些阻塞方法是不可中斷的,例如 I/O 阻塞和 synchronized 阻塞,需要針對某一些鎖或某一些 I/O 給出特定的方案。
- Object.wait()/wait(long)/wait(long, int)
- Thread.sleep(long)/sleep(long, int)
- Thread.join()/join(long)/join(long, int)
- java.util.concurrent.BlockingQueue.take()/put(E)
- java.util.concurrent.locks.Lock.lockInterruptibly()
- java.util.concurrent.CountDownLatch.await()
- java.util.concurrent.CyclicBarrier.await()
- java.util.concurrent.Exchanger.exchange(V)
- java.nio.channels.InterruptibleChannel 相關(guān)方法
- java.nio.channels.Selector 相關(guān)方法
3.5 InterruptedException 異常處理
3.5.1 傳遞中斷
當(dāng)在 run 中調(diào)用了一個(gè)有異常的方法時(shí),該異常應(yīng)該在方法中用 throws 聲明,傳遞到 run 方法,而不是在方法中捕獲,此時(shí)可能會造成不可預(yù)料的結(jié)果。throwInMethod2() 為正確做法,throwInMethod1() 為錯(cuò)誤做法。
public class HandleInterruptedException implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new HandleInterruptedException());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
@Override
public void run() {
while (true) {
System.out.println("work");
try {
throwInMethod2();
} catch (InterruptedException e) {
System.out.println("保存日志、停止程序");
e.printStackTrace();
}
}
/*while (true) {
System.out.println("go");
throwInMethod1();
}*/
}
private void throwInMethod2() throws InterruptedException {
Thread.sleep(2000);
}
private void throwInMethod1() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.5.2 重新設(shè)置中斷狀態(tài)
因?yàn)樽枞麙伋?InterruptedException 異常后,會清除中斷狀態(tài)??梢栽?catch 子語句中調(diào)用 Thread.currentThread().interrupt() 方法來恢復(fù)設(shè)置中斷狀態(tài),以便于在后續(xù)的執(zhí)行中,依然能夠檢查到剛才發(fā)生了中斷。
public class HandleInterruptedException2 implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new HandleInterruptedException2());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("發(fā)生中斷,程序運(yùn)行結(jié)束");
break;
}
System.out.println("work");
reInterrupt();
}
}
private void reInterrupt() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
《Java 并發(fā)編程的藝術(shù)》