一、線程的創(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í)行線程港粱。一種方法是定義一個 Thread 類的子類,該子類重寫 Thread 類的 run 方法。然后可以分配并啟動子類的實例猜极。創(chuàng)建線程的另一種方法是定義一個實現(xiàn) Runnable 接口的類延届,然后可以分配該類的實例,在創(chuàng)建 Thread 時將其作為參數(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();
}
}
定義一個類繼承 Thread 類耻姥,然后重寫 run 方法,直接通過該類的實例啟動線程有咨,輸出結(jié)果為:用 Thread 方式創(chuàng)建線程琐簇。
1.2 實現(xiàn) Runnable 接口
public class RunnableStyle implements Runnable {
@Override
public void run() {
System.out.println("用 Runnable 方式實現(xiàn)線程");
}
public static void main(String[] args) {
new Thread(new RunnableStyle()).start();
}
}
定義一個類實現(xiàn) Runnable 接口并重寫 run 方法,通過將該 Runnable 實例傳入 Thread 類參數(shù)中啟動線程摔吏,輸出結(jié)果為:用 Runnable 方式實現(xiàn)線程鸽嫂。
1.3 兩種方法對比
準確的講,創(chuàng)建線程都是通過構(gòu)造 Thread 類這一種方式實現(xiàn)征讲,實現(xiàn)線程的執(zhí)行單元有兩種方式据某。
public class Thread implements Runnable {
/** 省略 */
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
- 方法一(繼承 Thread 類):Thread 類本身也是實現(xiàn)了 Runnable 接口然后重寫 run 方法的,當(dāng)通過繼承 Thread 類創(chuàng)建線程時诗箍,Thread 類的整個 run 方法都會被重寫癣籽。
- 方法二(實現(xiàn) Runnable 接口):Thread 類中有一個名為 target 的 Runnable 變量,在 Thread(Runnable target) 構(gòu)造方法中傳入 Runnable 實例會初始化 target 屬性滤祖,通過該方法創(chuàng)建線程筷狼,最終只會調(diào)用 target.run() 方法,并不會重寫整個 run 方法匠童。
通過方法二創(chuàng)建線程的方式其實更好:
- 實現(xiàn) Runnable 接口的方式與 Thread 類解耦埂材。
- 接口可以多實現(xiàn),繼承 Thread 類的方式限制了可擴展性汤求。
- 繼承 Thread 類的話俏险,每次新建線程都會去創(chuàng)建一個獨立的線程,開銷大扬绪,不適合資源共享竖独。實現(xiàn) Runnable 接口的話,則很容易的實現(xiàn)資源共享挤牛,而且可以利用線程池等工具莹痢,大大減小創(chuàng)建線程和銷毀線程帶來的損耗。
同時使用這兩種方法創(chuàng)建線程:
先通過在 Thread 類構(gòu)造器中傳入匿名內(nèi)部類(Runnable 實例)的方式創(chuàng)建線程,然后在此基礎(chǔ)上重寫了 Thread 類的 run 方法竞膳,最終輸出:使用 Thread 方式創(chuàng)建航瞭。因為傳入 Runnable 實例創(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 傳入進去卻沒有運行
@Override
public void run() {
System.out.println("使用 Thread 方式創(chuàng)建");
}
}.start();
}
}
創(chuàng)建線程的方式通常分為兩類长窄,除此之外滔吠,通過實現(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òu)造的線程對象是由其父線程來進行空間分配的懂算,而子線程繼承了父線程的 daemon只冻、priority 等屬性,同時還會分配一個唯一的 id 來標識這個線程计技。
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.實現(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.使用定時器
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 來運行我們的線程垮媒,線程何時運行由線程調(diào)度器來確定舍悯。該線程啟動的同時會啟動兩個線程:第一個是用來執(zhí)行 start 方法的父線程或主線程,第二個是被創(chuàng)建的子線程睡雇。
準備工作:讓線程處于就緒狀態(tài)(已經(jīng)獲得了除 CPU 以外的其他資源萌衬,如已經(jīng)設(shè)置了上下文,線程狀態(tài)它抱,棧等)秕豫,做完準備工作后,才能被 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)進入到其他狀態(tài)沫屡,比如 RUNNABLE饵隙,只有處于 NEW 狀態(tài)的線程才能調(diào)用 start() 方法撮珠。
1.2 原理分析
通過 threadStatus 屬性來判斷是否重復(fù)啟動并拋出異常,實際的啟動方法是 native 方法 start0()。
public class Thread implements Runnable {
/**
* 線程狀態(tài)芯急,初始化為 0勺届,表示還未啟
*/
private volatile int threadStatus = 0;
public synchronized void start() {
// 判斷線程的狀態(tài),也就是判斷是否啟動娶耍,重復(fù)啟動時拋出 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 類的一個基本方法
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 方法才是真正意義上啟動了一個線程想鹰,會經(jīng)歷線程的各個生命周期紊婉,如果直接調(diào)用 run 方法,則只是普通的調(diào)用方法辑舷,不會通過子線程去調(diào)用喻犁。
三、線程的終止
1.過期的 suspend()何缓、resume()肢础、stop()
這三個方法已經(jīng)被廢除,通過查看 Oracle 官方文檔 可以得知碌廓。使用 stop() 方法停止線程會釋放線程的所有 monitor传轰,該方法在終止一個線程時不會保證線程的資源正常釋放,并且拋出 ThreadDeath 異常谷婆,通常是沒有給予線程完成資源釋放工作的機會路召,因此會導(dǎo)致程序出現(xiàn)數(shù)據(jù)不同步。suspend() 方法則容易造成死鎖波材,該方法在調(diào)用后股淡,線程不會釋放已經(jīng)占有的資源(比如鎖),而是占有著資源進入掛起狀態(tài)廷区。resume() 必須和 suspend() 一起使用唯灵,當(dāng)要恢復(fù)目標線程的線程在調(diào)用 resume 之前嘗試鎖定這個 monitor,此時就會導(dǎo)致死鎖隙轻。
2.volatile 標志位
通過 volatile 修飾的共享變量可以進行線程的終止埠帕。
2.1 成功案例
子線程每隔 1 秒輸出:持續(xù)運行。主線程在 2 秒后將 stop 置為 true玖绿,此時子線程 while 循環(huán)停止敛瓷,子線程運行結(jié)束。循環(huá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ù)運行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
TimeUnit.SECONDS.sleep(2);
stop = true;
}
}
運行結(jié)果如下:
持續(xù)運行
持續(xù)運行
2.3 失敗案例
使用 volatile 的局限性呐籽,當(dāng)線程陷入阻塞時,使用 volatile 修飾的變量無法停止線程。
通過生產(chǎn)者消費者例子來演示阻塞情況下 volatile 的局限性狡蝶,定義一個生產(chǎn)者類實現(xiàn) Runnable 接口重寫 run 方法庶橱,在 run 中當(dāng) volatile 修飾的 canceled 變量為 false 時,生產(chǎn)者通過 BlockingQueue 的 put 方法不斷添加數(shù)據(jù)贪惹,當(dāng)阻塞隊列到達上限時苏章,put 方法會阻塞。定義一個消費者類奏瞬,通過 needMoreCount 方法判斷消費者是否結(jié)束消費枫绅。
在主函數(shù)中初始化一個長度為 10 的阻塞隊列,構(gòu)建生產(chǎn)者和消費者實例硼端,當(dāng)消費者結(jié)束消費時撑瞧,將生產(chǎn)者的 canceled 屬性值改為 true,但是此時生產(chǎn)者仍然在運行显蝌,因為生產(chǎn)者線程阻塞在 put 方法预伺。這就是 volatile 標志位的局限性了。
public class WrongVolatileDemo {
public static void main(String[] args) throws InterruptedException {
// 定義容量為 10 的阻塞隊列
BlockingQueue<Integer> storage = new ArrayBlockingQueue<>(10);
// 啟動生產(chǎn)者線程
Thread producerThread = new Thread(new Producer(storage));
producerThread.start();
Thread.sleep(1000);
// 啟動消費者
Consumer consumer = new Consumer(storage);
while (consumer.needMoreCount()) {
System.out.println("消費者消費:" + consumer.getStorage().take());
Thread.sleep(100);
}
System.out.println("消費者消費完全結(jié)束");
// 此時生產(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) {
// 如果隊列滿的話曼尊,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)者停止運行");
}
}
}
/**
* 消費者
*/
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 翻譯為中斷酬诀,中斷可以理解為線程的一個標識位屬性,它表示一個運行中的線程是否被其他線程進行了中斷操作骆撇。中斷好比其他線程對該線程打了個招呼瞒御,其他線程通過調(diào)用該線程的 interrupt() 方法對其進行中斷操作。
舉幾個例子來演示 interrupt 的不同用法神郊。
3.1 不帶阻塞的中斷
該例子是最簡單的中斷肴裙,thread 線程啟動后,休眠 1ms 再調(diào)用該對象的 interrupt 方法涌乳,此時線程中正在執(zhí)行的循環(huán)檢測到 Thread.currentThread().isInterrupted() 為 true 結(jié)束循環(huán)蜻懦,輸出 count 變量的值。
當(dāng)線程調(diào)用自身的 interrupt 方法時夕晓,會將中斷標記設(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è)置中斷標記
thread.interrupt();
}
}
3.2 帶有阻塞的中斷
該例子演示帶有 sleep 阻塞的中斷方法使用征炼。sleep 方法使用需要拋出 InterruptedException,說明該方法可以響應(yīng) interrupt 中斷躬贡。在線程啟動后谆奥,該線程會休眠 1s,而主線程在休眠 100ms 后會調(diào)用中斷方法拂玻,此時該線程是處于阻塞狀態(tài)酸些,在阻塞狀態(tài)下響應(yīng)到中斷宰译,sleep 方法會拋出 InterruptedException ,但是在拋出該異常前擂仍,JVM 會先將該線程的中斷標識位清除,然后才拋出 InterruptedException熬甚,此時調(diào)用 isInterrupted() 方法將會返回 false逢渔。
如果在執(zhí)行過程中,每次循環(huán)都會調(diào)用 sleep 方法乡括,那么其實可以不需要每次迭代都通過 isInterrupted() 方法檢查中斷肃廓,因為 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("中斷標記:" + Thread.currentThread().isInterrupted());
}
});
thread.start();
Thread.sleep(100);
thread.interrupt();
}
}
運行結(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)
中斷標記:false
3.3 interrupt 相關(guān)方法
3.4.1 interrupt()
設(shè)置中斷標記诲泌,最終調(diào)用 native 的 interrupt0() 方法設(shè)置中斷標記盲赊。
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è)置中斷標記的方法葵第,Java 中的每個線程都與操作系統(tǒng)的線程一一對應(yīng)绘迁,一個 osthread 就對應(yīng) Java 中的一個線程,如果 osthread 沒有被設(shè)置為中斷卒密,則設(shè)置中斷標記為 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è)置中斷標記為 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)方法哮奇,兩個方法都調(diào)用了 isInterrupted 方法膛腐,不過傳入的參數(shù)不一樣,true 表示清除中斷狀態(tài)鼎俘,false 表示不清除哲身。
Thread.interrupted() 在哪個線程里被調(dià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è)置中斷標志
thread.interrupt();
// 獲取中斷標志律罢,被中斷了返回 true
System.out.println("isInterrupted: " + thread.isInterrupted());
// 獲取中斷標志并重置,interrupted 靜態(tài)方法調(diào)用的是執(zhí)行它的線程棍丐,也就是 main 線程误辑,返回 false
System.out.println("isInterrupted: " + thread.interrupted());
// 獲取中斷標志并重置,Main 函數(shù)沒有沒有被中斷歌逢,返回 false
System.out.println("isInterrupted: " + Thread.interrupted());
// 獲取中斷標志巾钉,中斷標記沒有被清除,返回 true
System.out.println("isInterrupted: " + thread.isInterrupted());
thread.join();
System.out.println("Main thread is over.");
}
}
運行結(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)用了一個有異常的方法時,該異常應(yīng)該在方法中用 throws 聲明赚导,傳遞到 run 方法茬缩,而不是在方法中捕獲,此時可能會造成不可預(yù)料的結(jié)果吼旧。throwInMethod2() 為正確做法凰锡,throwInMethod1() 為錯誤做法。
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)
因為阻塞拋出 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ā)生中斷欲诺,程序運行結(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ù)》