亚洲精品中文字幕无乱码_久久亚洲精品无码AV大片_最新国产免费Av网址_国产精品3级片

java語(yǔ)言

淺談Java線程中斷的本質(zhì)深入理解

時(shí)間:2024-08-23 15:07:03 java語(yǔ)言 我要投稿
  • 相關(guān)推薦

淺談Java線程中斷的本質(zhì)深入理解

  一、Java中斷的現(xiàn)象

  首先,看看Thread類里的幾個(gè)方法:

  public static booleaninterrupted測(cè)試當(dāng)前線程是否已經(jīng)中斷。線程的中斷狀態(tài)由該方法清除。換句話說(shuō),如果連續(xù)兩次調(diào)用該方法,則第二次調(diào)用將返回 false(在第一次調(diào)用已清除了其中斷狀態(tài)之后,且第二次調(diào)用檢驗(yàn)完中斷狀態(tài)前,當(dāng)前線程再次中斷的情況除外)。public booleanisInterrupted()測(cè)試線程是否已經(jīng)中斷。線程的中斷狀態(tài)不受該方法的影響。public voidinterrupt()中斷線程。

  上面列出了與中斷有關(guān)的幾個(gè)方法及其行為,可以看到interrupt是中斷線程。如果不了解Java的中斷機(jī)制,這樣的一種解釋極容易造成誤解,認(rèn)為調(diào)用了線程的interrupt方法就一定會(huì)中斷線程。

  其實(shí),Java的中斷是一種協(xié)作機(jī)制。也就是說(shuō)調(diào)用線程對(duì)象的interrupt方法并不一定就中斷了正在運(yùn)行的線程,它只是要求線程自己在合適的時(shí)機(jī)中斷自己。每個(gè)線程都有一個(gè)boolean的中斷狀態(tài)(不一定就是對(duì)象的屬性,事實(shí)上,該狀態(tài)也確實(shí)不是Thread的字段),interrupt方法僅僅只是將該狀態(tài)置為true

  復(fù)制代碼 代碼如下:

  public class TestInterrupt {

  public static void main(String[] args) {

  Thread t = new MyThread();

  t.start();

  t.interrupt();

  System.out.println("已調(diào)用線程的interrupt方法");

  }

  static class MyThread extends Thread {

  public void run() {

  int num = longTimeRunningNonInterruptMethod(2, 0);

  System.out.println("長(zhǎng)時(shí)間任務(wù)運(yùn)行結(jié)束,num=" + num);

  System.out.println("線程的中斷狀態(tài):" + Thread.interrupted());

  }

  private static int longTimeRunningNonInterruptMethod(int count, int initNum) {

  for(int i=0; i<count; i++) {

  for(int j=0; j<Integer.MAX_VALUE; j++) {

  initNum ++;

  }

  }

  return initNum;

  }

  }

  }

  一般情況下,會(huì)打印如下內(nèi)容:

  已調(diào)用線程的interrupt方法

  長(zhǎng)時(shí)間任務(wù)運(yùn)行結(jié)束,num=-2

  線程的中斷狀態(tài):true

  可見(jiàn),interrupt方法并不一定能中斷線程。但是,如果改成下面的程序,情況會(huì)怎樣呢?

  復(fù)制代碼 代碼如下:

  import java.util.concurrent.TimeUnit;

  public class TestInterrupt {

  public static void main(String[] args) {

  Thread t = new MyThread();

  t.start();

  t.interrupt();

  System.out.println("已調(diào)用線程的interrupt方法");

  }

  static class MyThread extends Thread {

  public void run() {

  int num = -1;

  try {

  num = longTimeRunningInterruptMethod(2, 0);

  } catch (InterruptedException e) {

  System.out.println("線程被中斷");

  throw new RuntimeException(e);

  }

  System.out.println("長(zhǎng)時(shí)間任務(wù)運(yùn)行結(jié)束,num=" + num);

  System.out.println("線程的中斷狀態(tài):" + Thread.interrupted());

  }

  private static int longTimeRunningInterruptMethod(int count, int initNum) throws InterruptedException{

  for(int i=0; i<count; i++) {

  TimeUnit.SECONDS.sleep(5);

  }

  return initNum;

  }

  }

  }

  經(jīng)運(yùn)行可以發(fā)現(xiàn),程序拋出異常停止了,run方法里的后兩條打印語(yǔ)句沒(méi)有執(zhí)行。那么,區(qū)別在哪里?

  一般說(shuō)來(lái),如果一個(gè)方法聲明拋出InterruptedException,表示該方法是可中斷的(沒(méi)有在方法中處理中斷卻也聲明拋出InterruptedException的除外),也就是說(shuō)可中斷方法會(huì)對(duì)interrupt調(diào)用做出響應(yīng)(例如sleep響應(yīng)interrupt的操作包括清除中斷狀態(tài),拋出InterruptedException),如果interrupt調(diào)用是在可中斷方法之前調(diào)用,可中斷方法一定會(huì)處理中斷,像上面的例子,interrupt方法極可能在run未進(jìn)入sleep的時(shí)候就調(diào)用了,但sleep檢測(cè)到中斷,就會(huì)處理該中斷。如果在可中斷方法正在執(zhí)行中的時(shí)候調(diào)用interrupt,會(huì)怎么樣呢?這就要看可中斷方法處理中斷的時(shí)機(jī)了,只要可中斷方法能檢測(cè)到中斷狀態(tài)為true,就應(yīng)該處理中斷。讓我們?yōu)殚_(kāi)頭的那段代碼加上中斷處理。

  那么自定義的可中斷方法該如何處理中斷呢?那就是在適合處理中斷的地方檢測(cè)線程中斷狀態(tài)并處理。

  復(fù)制代碼 代碼如下:

  public class TestInterrupt {

  public static void main(String[] args) throws Exception {

  Thread t = new MyThread();

  t.start();

  // TimeUnit.SECONDS.sleep(1);//如果不能看到處理過(guò)程中被中斷的情形,可以啟用這句再看看效果

  t.interrupt();

  System.out.println("已調(diào)用線程的interrupt方法");

  }

  static class MyThread extends Thread {

  public void run() {

  int num;

  try {

  num = longTimeRunningNonInterruptMethod(2, 0);

  } catch (InterruptedException e) {

  throw new RuntimeException(e);

  }

  System.out.println("長(zhǎng)時(shí)間任務(wù)運(yùn)行結(jié)束,num=" + num);

  System.out.println("線程的中斷狀態(tài):" + Thread.interrupted());

  }

  private static int longTimeRunningNonInterruptMethod(int count, int initNum) throws InterruptedException {

  if(interrupted()) {

  throw new InterruptedException("正式處理前線程已經(jīng)被請(qǐng)求中斷");

  }

  for(int i=0; i<count; i++) {

  for(int j=0; j<Integer.MAX_VALUE; j++) {

  initNum ++;

  }

  //假如這就是一個(gè)合適的地方

  if(interrupted()) {

  //回滾數(shù)據(jù),清理操作等

  throw new InterruptedException("線程正在處理過(guò)程中被中斷");

  }

  }

  return initNum;

  }

  }

  }

  如上面的代碼,方法longTimeRunningMethod此時(shí)已是一個(gè)可中斷的方法了。在進(jìn)入方法的時(shí)候判斷是否被請(qǐng)求中斷,如果是,就不進(jìn)行相應(yīng)的處理了;處理過(guò)程中,可能也有合適的地方處理中斷,例如上面最內(nèi)層循環(huán)結(jié)束后。

  這段代碼中檢測(cè)中斷用了Thread的靜態(tài)方法interrupted,它將中斷狀態(tài)置為false,并將之前的狀態(tài)返回,而isInterrupted只是檢測(cè)中斷,并不改變中斷狀態(tài)。一般來(lái)說(shuō),處理過(guò)了中斷請(qǐng)求,應(yīng)該將其狀態(tài)置為false。但具體還要看實(shí)際情形。

  二、Java中斷的本質(zhì)

  在歷史上,Java試圖提供過(guò)搶占式限制中斷,但問(wèn)題多多,例如已被廢棄的Thread.stop、Thread.suspend和 Thread.resume等。另一方面,出于Java應(yīng)用代碼的健壯性的考慮,降低了編程門檻,減少不清楚底層機(jī)制的程序員無(wú)意破壞系統(tǒng)的概率。

  如今,Java的線程調(diào)度不提供搶占式中斷,而采用協(xié)作式的中斷。其實(shí),協(xié)作式的中斷,原理很簡(jiǎn)單,就是輪詢某個(gè)表示中斷的標(biāo)記,我們?cè)谌魏纹胀ùa的中都可以實(shí)現(xiàn)。 例如下面的代碼:

  復(fù)制代碼 代碼如下:

  volatile bool isInterrupted;

  //…

  while(!isInterrupted) {

  compute();

  }

  但是,上述的代碼問(wèn)題也很明顯。當(dāng)compute執(zhí)行時(shí)間比較長(zhǎng)時(shí),中斷無(wú)法及時(shí)被響應(yīng)。另一方面,利用輪詢檢查標(biāo)志變量的方式,想要中斷wait和sleep等線程阻塞操作也束手無(wú)策。

  如果仍然利用上面的思路,要想讓中斷及時(shí)被響應(yīng),必須在虛擬機(jī)底層進(jìn)行線程調(diào)度的對(duì)標(biāo)記變量進(jìn)行檢查。是的,JVM中確實(shí)是這樣做的。下面摘自java.lang.Thread的源代碼:

  復(fù)制代碼 代碼如下:

  public static boolean interrupted() {

  return currentThread().isInterrupted(true);

  }

  //…

  private native boolean isInterrupted(boolean ClearInterrupted);

  可以發(fā)現(xiàn),isInterrupted被聲明為native方法,取決于JVM底層的實(shí)現(xiàn)。

  實(shí)際上,JVM內(nèi)部確實(shí)為每個(gè)線程維護(hù)了一個(gè)中斷標(biāo)記。但應(yīng)用程序不能直接訪問(wèn)這個(gè)中斷變量,必須通過(guò)下面幾個(gè)方法進(jìn)行操作:

  復(fù)制代碼 代碼如下:

  public class Thread {

  //設(shè)置中斷標(biāo)記

  public void interrupt() { ... }

  //獲取中斷標(biāo)記的值

  public boolean isInterrupted() { ... }

  //清除中斷標(biāo)記,并返回上一次中斷標(biāo)記的值

  public static boolean interrupted() { ... }

  ...

  }

  通常情況下,調(diào)用線程的interrupt方法,并不能立即引發(fā)中斷,只是設(shè)置了JVM內(nèi)部的中斷標(biāo)記。因此,通過(guò)檢查中斷標(biāo)記,應(yīng)用程序可以做一些特殊操作,也可以完全忽略中斷。

  你可能想,如果JVM只提供了這種簡(jiǎn)陋的中斷機(jī)制,那和應(yīng)用程序自己定義中斷變量并輪詢的方法相比,基本也沒(méi)有什么優(yōu)勢(shì)。

  JVM內(nèi)部中斷變量的主要優(yōu)勢(shì),就是對(duì)于某些情況,提供了模擬自動(dòng)“中斷陷入”的機(jī)制。

  在執(zhí)行涉及線程調(diào)度的阻塞調(diào)用時(shí)(例如wait、sleep和join),如果發(fā)生中斷,被阻塞線程會(huì)“盡可能快的”拋出InterruptedException。因此,我們就可以用下面的代碼框架來(lái)處理線程阻塞中斷:

  復(fù)制代碼 代碼如下:

  try {

  //wait、sleep或join

  }

  catch(InterruptedException e) {

  //某些中斷處理工作

  }

  所謂“盡可能快”,我猜測(cè)JVM就是在線程調(diào)度調(diào)度的間隙檢查中斷變量,速度取決于JVM的實(shí)現(xiàn)和硬件的性能。

  三、一些不會(huì)拋出 InterruptedException 的線程阻塞操作

  然而,對(duì)于某些線程阻塞操作,JVM并不會(huì)自動(dòng)拋出InterruptedException異常。例如,某些I/O操作和內(nèi)部鎖操作。對(duì)于這類操作,可以用其他方式模擬中斷:

  1)java.io中的異步socket I/O

  讀寫socket的時(shí)候,InputStream和OutputStream的read和write方法會(huì)阻塞等待,但不會(huì)響應(yīng)java中斷。不過(guò),調(diào)用Socket的close方法后,被阻塞線程會(huì)拋出SocketException異常。

  2)利用Selector實(shí)現(xiàn)的異步I/O

  如果線程被阻塞于Selector.select(在java.nio.channels中),調(diào)用wakeup方法會(huì)引起ClosedSelectorException異常。

  3)鎖獲取

  如果線程在等待獲取一個(gè)內(nèi)部鎖,我們將無(wú)法中斷它。但是,利用Lock類的lockInterruptibly方法,我們可以在等待鎖的同時(shí),提供中斷能力。

  四、兩條編程原則

  另外,在任務(wù)與線程分離的框架中,任務(wù)通常并不知道自身會(huì)被哪個(gè)線程調(diào)用,也就不知道調(diào)用線程處理中斷的策略。所以,在任務(wù)設(shè)置了線程中斷標(biāo)記后,并不能確保任務(wù)會(huì)被取消。因此,有以下兩條編程原則:

  1)除非你知道線程的中斷策略,否則不應(yīng)該中斷它。

  這條原則告訴我們,不應(yīng)該直接調(diào)用Executer之類框架中線程的interrupt方法,應(yīng)該利用諸如Future.cancel的方法來(lái)取消任務(wù)。

  2)任務(wù)代碼不該猜測(cè)中斷對(duì)執(zhí)行線程的含義。

  這條原則告訴我們,一般代碼遇在到InterruptedException異常時(shí),不應(yīng)該將其捕獲后“吞掉”,而應(yīng)該繼續(xù)向上層代碼拋出。

  總之,Java中的非搶占式中斷機(jī)制,要求我們必須改變傳統(tǒng)的搶占式中斷思路,在理解其本質(zhì)的基礎(chǔ)上,采用相應(yīng)的原則和模式來(lái)編程。

【淺談Java線程中斷的本質(zhì)深入理解】相關(guān)文章:

深入理解java的反射04-02

java的多線程12-04

java多線程03-27

最新的Java容器類的深入理解04-02

java語(yǔ)言的多線程11-25

java線程的幾種狀態(tài)12-14

淺談理解Java中的弱引用04-02

Java線程編程中的主線程詳細(xì)介紹04-02

Java Tomcat和激活MyEclips的深入理解04-02