詳解Java編程中對(duì)線(xiàn)程的中斷處理
1. 引言
當(dāng)我們點(diǎn)擊某個(gè)殺毒軟件的取消按鈕來(lái)停止查殺病毒時(shí),當(dāng)我們?cè)诳刂婆_(tái)敲入quit命令以結(jié)束某個(gè)后臺(tái)服務(wù)時(shí)……都需要通過(guò)一個(gè)線(xiàn)程去取消另一個(gè)線(xiàn)程正在執(zhí)行的任務(wù)。Java沒(méi)有提供一種安全直接的方法來(lái)停止某個(gè)線(xiàn)程,但是Java提供了中斷機(jī)制。
如果對(duì)Java中斷沒(méi)有一個(gè)全面的了解,可能會(huì)誤以為被中斷的線(xiàn)程將立馬退出運(yùn)行,但事實(shí)并非如此。中斷機(jī)制是如何工作的?捕獲或檢測(cè)到中斷后,是拋出InterruptedException還是重設(shè)中斷狀態(tài)以及在方法中吞掉中斷狀態(tài)會(huì)有什么后果?Thread.stop與中斷相比又有哪些異同?什么情況下需要使用中斷?本文將從以上幾個(gè)方面進(jìn)行描述。
2. 中斷的原理
Java中斷機(jī)制是一種協(xié)作機(jī)制,也就是說(shuō)通過(guò)中斷并不能直接終止另一個(gè)線(xiàn)程,而需要被中斷的線(xiàn)程自己處理中斷。這好比是家里的父母叮囑在外的子女要注意身體,但子女是否注意身體,怎么注意身體則完全取決于自己。
Java中斷模型也是這么簡(jiǎn)單,每個(gè)線(xiàn)程對(duì)象里都有一個(gè)boolean類(lèi)型的標(biāo)識(shí)(不一定就要是Thread類(lèi)的字段,實(shí)際上也的確不是,這幾個(gè)方法最終都是通過(guò)native方法來(lái)完成的),代表著是否有中斷請(qǐng)求(該請(qǐng)求可以來(lái)自所有線(xiàn)程,包括被中斷的線(xiàn)程本身)。例如,當(dāng)線(xiàn)程t1想中斷線(xiàn)程t2,只需要在線(xiàn)程t1中將線(xiàn)程t2對(duì)象的中斷標(biāo)識(shí)置為true,然后線(xiàn)程2可以選擇在合適的時(shí)候處理該中斷請(qǐng)求,甚至可以不理會(huì)該請(qǐng)求,就像這個(gè)線(xiàn)程沒(méi)有被中斷一樣。
java.lang.Thread類(lèi)提供了幾個(gè)方法來(lái)操作這個(gè)中斷狀態(tài),這些方法包括:
public static booleaninterrupted
測(cè)試當(dāng)前線(xiàn)程是否已經(jīng)中斷。線(xiàn)程的中斷狀態(tài) 由該方法清除。換句話(huà)說(shuō),如果連續(xù)兩次調(diào)用該方法,則第二次調(diào)用將返回 false(在第一次調(diào)用已清除了其中斷狀態(tài)之后,且第二次調(diào)用檢驗(yàn)完中斷狀態(tài)前,當(dāng)前線(xiàn)程再次中斷的情況除外)。
public booleanisInterrupted()
測(cè)試線(xiàn)程是否已經(jīng)中斷。線(xiàn)程的中斷狀態(tài)不受該方法的影響。
public void interrupt()
中斷線(xiàn)程。
其中,interrupt方法是唯一能將中斷狀態(tài)設(shè)置為true的方法。靜態(tài)方法interrupted會(huì)將當(dāng)前線(xiàn)程的中斷狀態(tài)清除,但這個(gè)方法的命名極不直觀,很容易造成誤解,需要特別注意。
上面的例子中,線(xiàn)程t1通過(guò)調(diào)用interrupt方法將線(xiàn)程t2的中斷狀態(tài)置為true,t2可以在合適的時(shí)候調(diào)用interrupted或isInterrupted來(lái)檢測(cè)狀態(tài)并做相應(yīng)的處理。
此外,類(lèi)庫(kù)中的有些類(lèi)的方法也可能會(huì)調(diào)用中斷,如FutureTask中的cancel方法,如果傳入的參數(shù)為true,它將會(huì)在正在運(yùn)行異步任務(wù)的線(xiàn)程上調(diào)用interrupt方法,如果正在執(zhí)行的異步任務(wù)中的代碼沒(méi)有對(duì)中斷做出響應(yīng),那么cancel方法中的參數(shù)將不會(huì)起到什么效果;又如ThreadPoolExecutor中的shutdownNow方法會(huì)遍歷線(xiàn)程池中的工作線(xiàn)程并調(diào)用線(xiàn)程的interrupt方法來(lái)中斷線(xiàn)程,所以如果工作線(xiàn)程中正在執(zhí)行的任務(wù)沒(méi)有對(duì)中斷做出響應(yīng),任務(wù)將一直執(zhí)行直到正常結(jié)束。
3. 中斷的處理
既然Java中斷機(jī)制只是設(shè)置被中斷線(xiàn)程的中斷狀態(tài),那么被中斷線(xiàn)程該做些什么?
處理時(shí)機(jī)
顯然,作為一種協(xié)作機(jī)制,不會(huì)強(qiáng)求被中斷線(xiàn)程一定要在某個(gè)點(diǎn)進(jìn)行處理。實(shí)際上,被中斷線(xiàn)程只需在合適的時(shí)候處理即可,如果沒(méi)有合適的時(shí)間點(diǎn),甚至可以不處理,這時(shí)候在任務(wù)處理層面,就跟沒(méi)有調(diào)用中斷方法一樣?!昂线m的時(shí)候”與線(xiàn)程正在處理的業(yè)務(wù)邏輯緊密相關(guān),例如,每次迭代的時(shí)候,進(jìn)入一個(gè)可能阻塞且無(wú)法中斷的方法之前等,但多半不會(huì)出現(xiàn)在某個(gè)臨界區(qū)更新另一個(gè)對(duì)象狀態(tài)的時(shí)候,因?yàn)檫@可能會(huì)導(dǎo)致對(duì)象處于不一致?tīng)顟B(tài)。
處理時(shí)機(jī)決定著程序的效率與中斷響應(yīng)的靈敏性。頻繁的檢查中斷狀態(tài)可能會(huì)使程序執(zhí)行效率下降,相反,檢查的較少可能使中斷請(qǐng)求得不到及時(shí)響應(yīng)。如果發(fā)出中斷請(qǐng)求之后,被中斷的線(xiàn)程繼續(xù)執(zhí)行一段時(shí)間不會(huì)給系統(tǒng)帶來(lái)災(zāi)難,那么就可以將中斷處理放到方便檢查中斷,同時(shí)又能從一定程度上保證響應(yīng)靈敏度的地方。當(dāng)程序的性能指標(biāo)比較關(guān)鍵時(shí),可能需要建立一個(gè)測(cè)試模型來(lái)分析最佳的中斷檢測(cè)點(diǎn),以平衡性能和響應(yīng)靈敏性。
一個(gè)線(xiàn)程在未正常結(jié)束之前, 被強(qiáng)制終止是很危險(xiǎn)的事情. 因?yàn)樗赡軒?lái)完全預(yù)料不到的嚴(yán)重后果. 所以你看到Thread.suspend, Thread.stop等方法都被Deprecated了.
那么不能直接把一個(gè)線(xiàn)程搞掛掉, 但有時(shí)候又有必要讓一個(gè)線(xiàn)程死掉, 或者讓它結(jié)束某種等待的狀態(tài) 該怎么辦呢? 優(yōu)雅的方法就是, 給那個(gè)線(xiàn)程一個(gè)中斷信號(hào), 讓它自己決定該怎么辦. 比如說(shuō), 在某個(gè)子線(xiàn)程中為了等待一些特定條件的到來(lái), 你調(diào)用了Thread.sleep(10000), 預(yù)期線(xiàn)程睡10秒之后自己醒來(lái), 但是如果這個(gè)特定條件提前到來(lái)的話(huà), 你怎么通知一個(gè)在睡覺(jué)的線(xiàn)程呢? 又比如說(shuō), 主線(xiàn)程通過(guò)調(diào)用子線(xiàn)程的join方法阻塞自己以等待子線(xiàn)程結(jié)束, 但是子線(xiàn)程運(yùn)行過(guò)程中發(fā)現(xiàn)自己沒(méi)辦法在短時(shí)間內(nèi)結(jié)束, 于是它需要想辦法告訴主線(xiàn)程別等我了. 這些情況下, 就需要中斷.
中斷是通過(guò)調(diào)用Thread.interrupt()方法來(lái)做的. 這個(gè)方法通過(guò)修改了被調(diào)用線(xiàn)程的中斷狀態(tài)來(lái)告知那個(gè)線(xiàn)程, 說(shuō)它被中斷了. 對(duì)于非阻塞中的線(xiàn)程, 只是改變了中斷狀態(tài), 即Thread.isInterrupted()將返回true; 對(duì)于可取消的阻塞狀態(tài)中的線(xiàn)程, 比如等待在這些函數(shù)上的線(xiàn)程, Thread.sleep(), Object.wait(), Thread.join(), 這個(gè)線(xiàn)程收到中斷信號(hào)后, 會(huì)拋出InterruptedException, 同時(shí)會(huì)把中斷狀態(tài)置回為false.
下面的程序會(huì)演示對(duì)非阻塞中的線(xiàn)程中斷:
public class Thread3 extends Thread{ public void run(){ while(true){ if(Thread.interrupted()){ System.out.println("Someone interrupted me."); } else{ System.out.println("Going..."); } long now = System.currentTimeMillis(); while(System.currentTimeMillis()-now<1000){ // 為了避免Thread.sleep()而需要捕獲InterruptedException而帶來(lái)的理解上的困惑, // 此處用這種方法空轉(zhuǎn)1秒 } } } public static void main(String[] args) throws InterruptedException { Thread3 t = new Thread3(); t.start(); Thread.sleep(3000); t.interrupt(); } }
下面的程序演示的是子線(xiàn)程通知父線(xiàn)程別等它了:
public class Thread4 extends Thread { private Thread parent; public Thread4(Thread parent){ this.parent = parent; } public void run() { while (true) { System.out.println("sub thread is running..."); long now = System.currentTimeMillis(); while (System.currentTimeMillis() - now < 2000) { // 為了避免Thread.sleep()而需要捕獲InterruptedException而帶來(lái)的理解上的困惑, // 此處用這種方法空轉(zhuǎn)2秒 } parent.interrupt(); } } public static void main(String[] args){ Thread4 t = new Thread4(Thread.currentThread()); t.start(); try { t.join(); } catch (InterruptedException e) { System.out.println("Parent thread will die..."); } } }
中斷狀態(tài)可以通過(guò) Thread.isInterrupted()來(lái)讀取,并且可以通過(guò)一個(gè)名為 Thread.interrupted()的靜態(tài)方法讀取和清除狀態(tài)(即調(diào)用該方法結(jié)束之后, 中斷狀態(tài)會(huì)變成false)。
由于處于阻塞狀態(tài)的線(xiàn)程 被中斷后拋出exception并置回中斷狀態(tài), 有時(shí)候是不利的, 因?yàn)檫@個(gè)中斷狀態(tài)可能會(huì)作為別的線(xiàn)程的判斷條件, 所以穩(wěn)妥的辦法是在處理exception的地方把狀態(tài)復(fù)位:
boolean interrupted = false; try { while (true) { try { return blockingQueue.take(); } catch (InterruptedException e) { interrupted = true; } } } finally { if (interrupted){ Thread.currentThread().interrupt(); } }
當(dāng)代碼調(diào)用中須要拋出一個(gè)InterruptedException, 你可以選擇把中斷狀態(tài)復(fù)位, 也可以選擇向外拋出InterruptedException, 由外層的調(diào)用者來(lái)決定.
不是所有的阻塞方法收到中斷后都可以取消阻塞狀態(tài), 輸入和輸出流類(lèi)會(huì)阻塞等待 I/O 完成,但是它們不拋出 InterruptedException,而且在被中斷的情況下也不會(huì)退出阻塞狀態(tài).
嘗試獲取一個(gè)內(nèi)部鎖的操作(進(jìn)入一個(gè) synchronized 塊)是不能被中斷的,但是 ReentrantLock 支持可中斷的獲取模式即 tryLock(long time, TimeUnit unit)。
處理方式
(1)、 中斷狀態(tài)的管理
一般說(shuō)來(lái),當(dāng)可能阻塞的方法聲明中有拋出InterruptedException則暗示該方法是可中斷的,如BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep等,如果程序捕獲到這些可中斷的阻塞方法拋出的InterruptedException或檢測(cè)到中斷后,這些中斷信息該如何處理?一般有以下兩個(gè)通用原則:
如果遇到的是可中斷的阻塞方法拋出InterruptedException,可以繼續(xù)向方法調(diào)用棧的上層拋出該異常,如果是檢測(cè)到中斷,則可清除中斷狀態(tài)并拋出InterruptedException,使當(dāng)前方法也成為一個(gè)可中斷的方法。
若有時(shí)候不太方便在方法上拋出InterruptedException,比如要實(shí)現(xiàn)的某個(gè)接口中的方法簽名上沒(méi)有throws InterruptedException,這時(shí)就可以捕獲可中斷方法的InterruptedException并通過(guò)Thread.currentThread.interrupt()來(lái)重新設(shè)置中斷狀態(tài)。如果是檢測(cè)并清除了中斷狀態(tài),亦是如此。
一般的代碼中,尤其是作為一個(gè)基礎(chǔ)類(lèi)庫(kù)時(shí),絕不應(yīng)當(dāng)吞掉中斷,即捕獲到InterruptedException后在catch里什么也不做,清除中斷狀態(tài)后又不重設(shè)中斷狀態(tài)也不拋出InterruptedException等。因?yàn)橥痰糁袛酄顟B(tài)會(huì)導(dǎo)致方法調(diào)用棧的上層得不到這些信息。
當(dāng)然,凡事總有例外的時(shí)候,當(dāng)你完全清楚自己的方法會(huì)被誰(shuí)調(diào)用,而調(diào)用者也不會(huì)因?yàn)橹袛啾煌痰袅硕龅铰闊?,就可以這么做。
總得來(lái)說(shuō),就是要讓方法調(diào)用棧的上層獲知中斷的發(fā)生。假設(shè)你寫(xiě)了一個(gè)類(lèi)庫(kù),類(lèi)庫(kù)里有個(gè)方法amethod,在amethod中檢測(cè)并清除了中斷狀態(tài),而沒(méi)有拋出InterruptedException,作為amethod的用戶(hù)來(lái)說(shuō),他并不知道里面的細(xì)節(jié),如果用戶(hù)在調(diào)用amethod后也要使用中斷來(lái)做些事情,那么在調(diào)用amethod之后他將永遠(yuǎn)也檢測(cè)不到中斷了,因?yàn)橹袛嘈畔⒁呀?jīng)被amethod清除掉了。如果作為用戶(hù),遇到這樣有問(wèn)題的類(lèi)庫(kù),又不能修改代碼,那該怎么處理?只好在自己的類(lèi)里設(shè)置一個(gè)自己的中斷狀態(tài),在調(diào)用interrupt方法的時(shí)候,同時(shí)設(shè)置該狀態(tài),這實(shí)在是無(wú)路可走時(shí)才使用的方法。
(2)、 中斷的響應(yīng)
程序里發(fā)現(xiàn)中斷后該怎么響應(yīng)?這就得視實(shí)際情況而定了。有些程序可能一檢測(cè)到中斷就立馬將線(xiàn)程終止,有些可能是退出當(dāng)前執(zhí)行的任務(wù),繼續(xù)執(zhí)行下一個(gè)任務(wù)……作為一種協(xié)作機(jī)制,這要與中斷方協(xié)商好,當(dāng)調(diào)用interrupt會(huì)發(fā)生些什么都是事先知道的,如做一些事務(wù)回滾操作,一些清理工作,一些補(bǔ)償操作等。若不確定調(diào)用某個(gè)線(xiàn)程的interrupt后該線(xiàn)程會(huì)做出什么樣的響應(yīng),那就不應(yīng)當(dāng)中斷該線(xiàn)程。
4. Thread.interrupt VS Thread.stop
Thread.stop方法已經(jīng)不推薦使用了。而在某些方面Thread.stop與中斷機(jī)制有著相似之處。如當(dāng)線(xiàn)程在等待內(nèi)置鎖或IO時(shí),stop跟interrupt一樣,不會(huì)中止這些操作;當(dāng)catch住stop導(dǎo)致的異常時(shí),程序也可以繼續(xù)執(zhí)行,雖然stop本意是要停止線(xiàn)程,這么做會(huì)讓程序行為變得更加混亂。
那么它們的區(qū)別在哪里?最重要的就是中斷需要程序自己去檢測(cè)然后做相應(yīng)的處理,而Thread.stop會(huì)直接在代碼執(zhí)行過(guò)程中拋出ThreadDeath錯(cuò)誤,這是一個(gè)java.lang.Error的子類(lèi)。
在繼續(xù)之前,先來(lái)看個(gè)小例子:
package com.ticmy.interrupt; import java.util.Arrays; import java.util.Random; import java.util.concurrent.TimeUnit; public class TestStop { private static final int[] array = new int[80000]; private static final Thread t = new Thread() { public void run() { try { System.out.println(sort(array)); } catch (Error err) { err.printStackTrace(); } System.out.println("in thread t"); } }; static { Random random = new Random(); for(int i = 0; i < array.length; i++) { array[i] = random.nextInt(i + 1); } } private static int sort(int[] array) { for (int i = 0; i < array.length-1; i++){ for(int j = 0 ;j < array.length - i - 1; j++){ if(array[j] < array[j + 1]){ int temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; } } } return array[0]; } public static void main(String[] args) throws Exception { t.start(); TimeUnit.SECONDS.sleep(1); System.out.println("go to stop thread t"); t.stop(); System.out.println("finish main"); } }
這個(gè)例子很簡(jiǎn)單,線(xiàn)程t里面做了一個(gè)非常耗時(shí)的排序操作,排序方法中,只有簡(jiǎn)單的加、減、賦值、比較等操作,一個(gè)可能的執(zhí)行結(jié)果如下:
go to stop thread t java.lang.ThreadDeath at java.lang.Thread.stop(Thread.java:758) at com.ticmy.interrupt.TestStop.main(TestStop.java:44) finish main in thread t
這里sort方法是個(gè)非常耗時(shí)的操作,也就是說(shuō)主線(xiàn)程休眠一秒鐘后調(diào)用stop的時(shí)候,線(xiàn)程t還在執(zhí)行sort方法。就是這樣一個(gè)簡(jiǎn)單的方法,也會(huì)拋出錯(cuò)誤!換一句話(huà)說(shuō),調(diào)用stop后,大部分Java字節(jié)碼都有可能拋出錯(cuò)誤,哪怕是簡(jiǎn)單的加法!
如果線(xiàn)程當(dāng)前正持有鎖,stop之后則會(huì)釋放該鎖。由于此錯(cuò)誤可能出現(xiàn)在很多地方,那么這就讓編程人員防不勝防,極易造成對(duì)象狀態(tài)的不一致。例如,對(duì)象obj中存放著一個(gè)范圍值:最小值low,最大值high,且low不得大于high,這種關(guān)系由鎖lock保護(hù),以避免并發(fā)時(shí)產(chǎn)生競(jìng)態(tài)條件而導(dǎo)致該關(guān)系失效。假設(shè)當(dāng)前l(fā)ow值是5,high值是10,當(dāng)線(xiàn)程t獲取lock后,將low值更新為了15,此時(shí)被stop了,真是糟糕,如果沒(méi)有捕獲住stop導(dǎo)致的Error,low的值就為15,high還是10,這導(dǎo)致它們之間的小于關(guān)系得不到保證,也就是對(duì)象狀態(tài)被破壞了!如果在給low賦值的時(shí)候catch住stop導(dǎo)致的Error則可能使后面high變量的賦值繼續(xù),但是誰(shuí)也不知道Error會(huì)在哪條語(yǔ)句拋出,如果對(duì)象狀態(tài)之間的關(guān)系更復(fù)雜呢?這種方式幾乎是無(wú)法維護(hù)的,太復(fù)雜了!如果是中斷操作,它決計(jì)不會(huì)在執(zhí)行l(wèi)ow賦值的時(shí)候拋出錯(cuò)誤,這樣程序?qū)τ趯?duì)象狀態(tài)一致性就是可控的。
正是因?yàn)榭赡軐?dǎo)致對(duì)象狀態(tài)不一致,stop才被禁用。
5. 中斷的使用
通常,中斷的使用場(chǎng)景有以下幾個(gè):
點(diǎn)擊某個(gè)桌面應(yīng)用中的取消按鈕時(shí);
某個(gè)操作超過(guò)了一定的執(zhí)行時(shí)間限制需要中止時(shí);
多個(gè)線(xiàn)程做相同的事情,只要一個(gè)線(xiàn)程成功其它線(xiàn)程都可以取消時(shí);
一組線(xiàn)程中的一個(gè)或多個(gè)出現(xiàn)錯(cuò)誤導(dǎo)致整組都無(wú)法繼續(xù)時(shí);
當(dāng)一個(gè)應(yīng)用或服務(wù)需要停止時(shí)。
下面來(lái)看一個(gè)具體的例子。這個(gè)例子里,本打算采用GUI形式,但考慮到GUI代碼會(huì)使程序復(fù)雜化,就使用控制臺(tái)來(lái)模擬下核心的邏輯。這里新建了一個(gè)磁盤(pán)文件掃描的任務(wù),掃描某個(gè)目錄下的所有文件并將文件路徑打印到控制臺(tái),掃描的過(guò)程可能會(huì)很長(zhǎng)。若需要中止該任務(wù),只需在控制臺(tái)鍵入quit并回車(chē)即可。
package com.ticmy.interrupt; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; public class FileScanner { private static void listFile(File f) throws InterruptedException { if(f == null) { throw new IllegalArgumentException(); } if(f.isFile()) { System.out.println(f); return; } File[] allFiles = f.listFiles(); if(Thread.interrupted()) { throw new InterruptedException("文件掃描任務(wù)被中斷"); } for(File file : allFiles) { //還可以將中斷檢測(cè)放到這里 listFile(file); } } public static String readFromConsole() { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); try { return reader.readLine(); } catch (Exception e) { e.printStackTrace(); return ""; } } public static void main(String[] args) throws Exception { final Thread fileIteratorThread = new Thread() { public void run() { try { listFile(new File("c:\\")); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread() { public void run() { while(true) { if("quit".equalsIgnoreCase(readFromConsole())) { if(fileIteratorThread.isAlive()) { fileIteratorThread.interrupt(); return; } } else { System.out.println("輸入quit退出文件掃描"); } } } }.start(); fileIteratorThread.start(); } }
在掃描文件的過(guò)程中,對(duì)于中斷的檢測(cè)這里采用的策略是,如果碰到的是文件就不檢測(cè)中斷,是目錄才檢測(cè)中斷,因?yàn)槲募赡苁欠浅6嗟?,每次遇到文件都檢測(cè)一次會(huì)降低程序執(zhí)行效率。此外,在fileIteratorThread線(xiàn)程中,僅是捕獲了InterruptedException,沒(méi)有重設(shè)中斷狀態(tài)也沒(méi)有繼續(xù)拋出異常,因?yàn)槲曳浅G宄氖褂铆h(huán)境,run方法的調(diào)用棧上層已經(jīng)沒(méi)有可能需要檢測(cè)中斷狀態(tài)的方法了。
在這個(gè)程序中,輸入quit完全可以執(zhí)行System.exit(0)操作來(lái)退出程序,但正如前面提到的,這是個(gè)GUI程序核心邏輯的模擬,在GUI中,執(zhí)行System.exit(0)會(huì)使得整個(gè)程序退出。
- Java線(xiàn)程中斷的本質(zhì)深入理解
- Java多線(xiàn)程之中斷線(xiàn)程(Interrupt)的使用詳解
- java線(xiàn)程阻塞中斷與LockSupport使用介紹
- Java多線(xiàn)程中斷機(jī)制三種方法及示例
- Java中實(shí)現(xiàn)線(xiàn)程的超時(shí)中斷方法實(shí)例
- 深入Java線(xiàn)程中斷的本質(zhì)與編程原則的概述
- java多線(xiàn)程中斷代碼詳解
- 淺析Java線(xiàn)程的中斷機(jī)制
- 詳解Java線(xiàn)程中斷知識(shí)點(diǎn)
- 淺析java線(xiàn)程中斷的辦法
- Java檢測(cè)線(xiàn)程中斷狀態(tài)的方法示例
相關(guān)文章
java獲取新insert數(shù)據(jù)自增id的實(shí)現(xiàn)方法
這篇文章主要介紹了java獲取新insert數(shù)據(jù)自增id的實(shí)現(xiàn)方法,在具體生成id的時(shí)候,我們的操作順序一般是:先在主表中插入記錄,然后獲得自動(dòng)生成的id,以它為基礎(chǔ)插入從表的記錄,需要的朋友可以參考下2019-06-06Spring boot實(shí)現(xiàn)應(yīng)用打包部署的示例
本篇文章主要介紹了Spring boot實(shí)現(xiàn)應(yīng)用打包部署的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11Mybatis-Plus字段策略FieldStrategy的使用
本文主要介紹了Mybatis-Plus字段策略FieldStrategy的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08解決從Map、JSONObject取不存在鍵值對(duì)時(shí)的異常情況
這篇文章主要介紹了解決從Map、JSONObject取不存在鍵值對(duì)時(shí)的異常情況,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07地址到經(jīng)緯度坐標(biāo)轉(zhuǎn)化的JAVA代碼
這篇文章介紹了地址到經(jīng)緯度坐標(biāo)轉(zhuǎn)化的JAVA代碼,有需要的朋友可以參考一下2013-09-09Spring boot AOP通過(guò)XML配置文件聲明的方法
這篇文章主要介紹了Spring boot AOP通過(guò)XML配置文件聲明,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06