在很多時候我們需要中斷或者取消一些任務,在Java中并沒有提供一些好的方法來終止線程刻恭。
中斷原因
我們的任務或者線程一般情況下都會讓它正常的執(zhí)行直到結束尸执,然而有時候也有可能我們需要提前結束這個任務或者線程峰伙。但是一個任務或者線程的執(zhí)行涉及系統、內存程帕、棧住练、數據等的完整性,要提前結束并不簡單愁拭,在Java中也沒有提供好的安全地終止線程讲逛。
取消的原因有很多比如用戶取消、一定時間未成功提前結束敛苇、錯誤妆绞、服務器關閉等,一個好的軟件特點之一就是能夠很完善的處理失敗枫攀、關閉和取消的過程括饶。
中斷方法一:中斷標志位
我們可以設置一個已關閉的標志位,當任務或者線程運行的時候先判斷標志位的狀態(tài)来涨,如果是已經關閉那個這個任務或者線程就直接結束图焰,不過這個標志位需要用volatile關鍵字修飾,否則可能其他線程已經修改了任務可能仍然在運行蹦掐。
這種方法可以解決一部分問題技羔,但是當任務可能會被阻塞的時候就會出現問題,就像之前的生產者卧抗、消費者模式藤滥,如果生產者通過循環(huán)往隊列里面加元素,在每次循環(huán)之前都要判斷中斷標志位社裆,如果結束了就不往隊列中put數據了拙绊,當消費者在某些情況下可能不在消費數據所以會設置標志位為已結束。此時如果阻塞隊列是滿的泳秀,而剛好生產者在put阻塞中标沪,由于消費者不在消費,生產者線程就會永遠處于阻塞狀態(tài)嗜傅。
中斷方法二:Thread.interrupt()
上一個中斷方法在遇到阻塞方法時就會出現永久阻塞狀態(tài)的問題金句,所以Java的Thread提供了一個boolean類型的中斷狀態(tài),通過interrupt方法可以設置狀態(tài)為中斷吕嘀,isInterrupted方法會返回中斷狀態(tài)违寞,靜態(tài)的interrupted方法會清除當前線程的中斷狀態(tài)贞瞒,并返回它之前的值,這是清除中斷狀態(tài)的唯一方法坞靶。
一些阻塞方法如Thread.sleep憔狞、Object.wait蝴悉、阻塞隊列的take彰阴、put方法都會檢查線程中斷狀態(tài),并且在發(fā)現中斷時提前返回拍冠,他們響應中斷時執(zhí)行的操作包括:清除中斷狀態(tài)尿这,拋出InterruptedException,表示阻塞操作因為中斷而提前操作庆杜,這就是這些方法都會拋出InterruptedException的原因射众,反之如果拋出InterruptedException則說明線程被中斷,接受到這個信息后我們可以方便的處理后續(xù)流程晃财。
interrupt方法并不會真正的中斷一個正在運行的線程叨橱,而只是發(fā)出中斷請求,然后由線程在下一個合適的時刻中斷自己断盛,這樣才能保證數據結構不會被破壞罗洗。同時要小心調用interrupted方法,它會清除當前線程的中斷狀態(tài)钢猛,所以在方法的返回值是true時除非你本來想屏蔽這個中斷伙菜,否則必須要處理,可以拋出InterruptedException或者再次調用interrupt中斷線程命迈。
中斷方法三:利用Future
可以把任務封裝成一個Future贩绕,Future中有一個方法“boolean cancel(boolean mayInterruptIfRunning);”如果參數為true并且任務正在某個線程中執(zhí)行,那么這個線程就能夠被中斷壶愤,如果參數為false則表示如果任務還沒有運行那就不要運行(有些任務不處理中斷)淑倾。
還存在一些情況可能通過以上方法仍然無法中斷或取消,比如IO阻塞或者等待獲取鎖的阻塞征椒,不過Java中也有一些解決辦法娇哆,比如Socket則是可以通過關閉socket,對于鎖可以在Lock類中提供了lockInterruptibly方法陕靠,它可以支持在等在一個鎖的同時去響應中斷信號迂尝。
JVM的關閉
JVM關閉也分為正常關閉和強行關閉,強行關閉方式比如殺死JVM進程剪芥。這里主要說明的是正常關閉垄开,比如System.exit獲取程序正常執(zhí)行結束。在正常關閉時税肪,JVM會首先調用注冊的關閉鉤子(通過“Runtime.getRuntime().addShutdownHook();”添加的一些關閉程序時必須執(zhí)行的方法溉躲,比如清理一些臨時文件之類的)榜田,當鉤子關閉完成后JVM會運行終結器,最后停止锻梳。JVM關閉并不會關閉正在運行中的線程箭券,而是在JVM關閉完成后強行關閉。如果關閉鉤子或者終結器沒有執(zhí)行完成疑枯,那么JVM的關閉進程就會被掛起辩块,然后強行關閉JVM。
兩個重要知識點:
線程分兩種:普通線程和守護線程荆永,jvm啟動時創(chuàng)建的線程除了主線程外都是守護線程废亭,當創(chuàng)建一個線程時新線程會繼承狀態(tài),所以主線程默認情況下創(chuàng)建的線程都是普通線程具钥。
當一個線程退出時JVM會檢查其他正在運行的線程豆村,如果所有都是守護線程,那么JVM會執(zhí)行正常退出操作骂删。
終結器:垃圾回收器對那些定義了finalize方法的對象在回收后會調用finalize方法掌动,不過finalize方法有性能問題,所以現在已經避免使用宁玫。
總結
啟動一個任務或線程很簡單粗恢,但是要提前取消或者關閉卻比較復雜,一個好的軟件是應該能夠很好的支持取消和關閉的撬统。
Java程序員日常學習筆記适滓,如理解有誤歡迎各位交流討論!