高併發編程從入門到精通(四)

面試中最常被虐的地方必定有併發編程這塊知識點,不管你是剛剛入門的大四萌新仍是2-3年經驗的CRUD怪,也就是說這類問題你最起碼會被問3年,何不花時間死磕到底。消除恐懼最好的辦法就是面對他,奧利給!(這一系列是本人學習過程當中的筆記和總結,並提供調試代碼供你們玩耍java

上章回顧

1.sleep對象的monitor lock有釋放嗎?如何證實?git

2.sleep(0)是什麼意思?github

3.線程如何正常關閉?面試

4.join內部是經過那個函數來實現順序執行的?編程

請自行回顧以上問題,若是還有疑問的自行回顧上一章哦~bash

本章提要

本章內容原本是要和上一章一塊兒講的,可是因爲篇幅過長涉及到的內容較多,而且也有不少實戰代碼的調試,因此決定仍是另開一章來說比較好一些。本章主要介紹interrupt這個API。學習完成,同窗們會對interrupt有深刻了解,明白其內部的處理機制,同時對beginend也有較爲深入的認識。併發

本章代碼下載less

Interrupt

在本章中咱們一共要講解與interrupt相關的三個API方法,這個API我學的時候也是費了很多勁,因此相對來講會多寫一點,同窗們先泡杯茶慢慢看呀~😄異步

  • public void interrupt()
  • public boolean isInterrupted()
  • public static boolean interrupted()

interrupt

老套路,直接上API接口說明socket

* @revised 6.0
     * @spec JSR-51
     */
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
複製代碼

這麼長篇幅的介紹莫怕,咱們一塊兒來看這貨到底在說什麼鳥語。

首先,第一段句歸納了這個接口的做用中斷此線程

* Interrupts this thread.
複製代碼

第二段

//除非當前線程本身把本身中斷了,不然的話會一直容許checkAccess()調用
    //而且在調用的時候可能還會拋出SecurityException異常
     * <p> Unless the current thread is interrupting itself, which is
     * always permitted, the {@link #checkAccess() checkAccess} method
     * of this thread is invoked, which may cause a {@link
     * SecurityException} to be thrown.
複製代碼

這個checkAccess()咱們這邊不作深刻探討,咱們只要知道這是一個校驗接口,打個比方,好比在中斷JVM自身線程的時候,它就會去查詢是否有足夠權限來執行這個命令

第三段

含義就是線程在調用下列方法以後能夠調用interrupt來中斷狀態

*Object的wait方法

*Object的wait(long)方法

*Object的wait(long,int)方法

*Thread的sleep(long)方法

*Thread的sleep(long,int)方法

*Thread的join方法

*Thread的join(long)方法

*Thread的join(long,int)方法

*InterruptibleChannel的io操做

*Selector的wakeup方法

*其餘方法

第四段

//在線程因爲I/O操做而致使阻塞的時候,阻塞通道會被關閉,而後線程中斷標誌被設置
//爲true而且同時會收到ClosedByInterruptException異常
 * <p> If this thread is blocked in an I/O operation upon an {@link
     * java.nio.channels.InterruptibleChannel InterruptibleChannel}
     * then the channel will be closed, the thread's interrupt * status will be set, and the thread will receive a {@link * java.nio.channels.ClosedByInterruptException}. 複製代碼

這部分的內容後面會用一些篇幅來進一步說明,這裏就不展開來說。

第五段

//當前線程因爲調用Selector選擇器方法而阻塞的時候,線程中斷標誌會被設置爲true
//而且當前線程會立馬返回
  * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
     * then the thread's interrupt status will be set and it will return * immediately from the selection operation, possibly with a non-zero * value, just as if the selector's {@link
     * java.nio.channels.Selector#wakeup wakeup} method were invoked.
複製代碼

最後一段

//中斷一個不活躍的線程不會產生任何結果
Interrupting a thread that is not alive need not have any effect.
複製代碼

好到這裏你們應該已經等不及上手一試了,咱們先來一個常規的中斷sleep的操做

private static class MySleep implements Runnable {

    @Override
    public void run() {
      System.out.println("我開始睡覺了,誰也別來吵我");
      try {
        TimeUnit.HOURS.sleep(12);
      } catch (InterruptedException e) {
        System.out.println("你代碼炸啦!!!");
        e.printStackTrace();
      }
      System.out.println("起牀");
    }
  }

  public static void main(String[] args) {
    Thread thread = new Thread(new MySleep());
    thread.start();
    //調用中斷方法
    thread.interrupt();
  }
複製代碼

輸出:

我開始睡覺了,誰也別來吵我
你代碼炸啦!!!
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at src.com.lyf.page3.InterruptSleep$MySleep.run(InterruptSleep.java:19)
	at java.lang.Thread.run(Thread.java:748)
起牀
複製代碼

能夠看到這邊咱們打算睡它個12小時,可是好景不長,因爲代碼炸了,咱們只能立馬起牀幹活。好的這是一個悲傷的故事,但願你們都不要遇到😂。

這邊咱們注意到,在調用interrupt中斷了sleep以後,咱們的線程接收到了一個java.lang.InterruptedException異常,這裏須要咱們對異常作友好處理。不管你是try起來仍是直接往外拋,可是捕獲必定要作好,否則咱們對日誌會出現一堆java.lang.InterruptedException異常。


以上咱們已經基本學會了interrupt的使用,如今咱們追蹤到方法內部看看作了哪些操做。這裏咱們把這段代碼分紅三部分來說。

public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
複製代碼

(1)判斷中斷的線程和Thread.currentThread()指向是否是同一個

if (this != Thread.currentThread())
            checkAccess();
複製代碼

判斷當前中斷的線程是否是本身,不少同窗這裏可能會有疑問,thisThread.currentThread()難道指向的不是同一個對象嗎?

咱們用測試代碼來試驗一下

private static class MySleep implements Runnable {

    @Override
    public void run() {
      System.out.println("下班啦,我要睡覺了,誰也別來吵我");
      try {
        TimeUnit.HOURS.sleep(12);
      } catch (InterruptedException e) {
        System.out.println("你代碼炸啦!!!");
//        e.printStackTrace();
      }
      System.out.println("起牀");
    }
  }


  public static void main(String[] args) {

    Thread t1 = new Thread(() -> {
      Thread t2 = new Thread(new MySleep(), "一號");
      t2.start();
      t2.interrupt();
    }, "二號");
    t1.start();
  }
複製代碼

打斷點咱們能夠看到結果

這裏的做用其實就是判斷一號線程是否是在二號線程內部執行的,若是是那麼一號線程就是依託與二號線程而存活的,因此這裏須要調用 checkAccess()方法來進行校驗。

(2)判斷是不是I/O阻塞而觸發的線程中斷

synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
複製代碼

無論是調用sleep仍是wait咱們都會發現始終不會進入到這塊到邏輯代碼裏面,緣由就是blocker是null並無被設值。那麼blocker究竟是何時被設值到呢?

⚠️方法論來了

當接口內部有部分代碼調試不到或者代碼沒法理解當時候,咱們能夠回過頭去看接口說明。

這裏咱們再來看下這段說明

//在線程因爲I/O操做而致使阻塞的時候,阻塞通道會被關閉,而後線程中斷標誌被設置
//爲true而且同時會收到ClosedByInterruptException異常
 * <p> If this thread is blocked in an I/O operation upon an {@link
     * java.nio.channels.InterruptibleChannel InterruptibleChannel}
     * then the channel will be closed, the thread's interrupt * status will be set, and the thread will receive a {@link * java.nio.channels.ClosedByInterruptException}. 複製代碼

這裏咱們給同窗們提供一個案例來調試這塊代碼,代碼內容以下:

//src.com.lyf.page3.InterruptNIO
public static void main(String[] args) throws IOException {
    ServerSocket server = new ServerSocket(8080);
    InetSocketAddress isa = new InetSocketAddress("localhost", 8080);
    SocketChannel sc1 = SocketChannel.open(isa);
    SocketChannel sc2 = SocketChannel.open(isa);

    Thread t1 = new Thread(new NIOBlockExample(sc2), "一號");
    t1.start();

    Thread t2 = new Thread(new NIOBlockExample(sc1), "二號");
    t2.start();
    //打斷標誌
    t1.interrupt();
    t2.interrupt();
  }
複製代碼
//src.com.lyf.page3.NIOBlockExample
private final SocketChannel socketChannel;

  public NIOBlockExample(SocketChannel sc) {
    this.socketChannel = sc;
  }

  @Override
  public void run() {
    try {
      System.out.println("Waiting for read() in " + this);
      socketChannel.read(ByteBuffer.allocate(1));
    } catch (ClosedByInterruptException e) {
      e.printStackTrace();
      System.out.println("ClosedByInterruptException");
    } catch (AsynchronousCloseException e) {
      e.printStackTrace();
      System.out.println("AsynchronousCloseException");
    } catch (IOException e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }
    System.out.println("Exiting NIOBlocked.run() " + this);
  }
複製代碼

這裏代碼比較多建議你們對照src.com.lyf.page3.InterruptNIOsrc.com.lyf.page3.NIOBlockExample一邊調試一邊喝茶來細品下面這塊內容。

(1) 建立NIOBlockExample

首先咱們建立一個NIOBlockExample線程對象,經過調用SocketChannel.read來模擬可能產生I/O阻塞的場景,這裏同窗們不須要過於關注SocketChannel.read這個方法,咱們這邊只須要知道這是一個讀操做就行,這裏咱們的目的是爲了模擬I/O阻塞場景來調試interrupt代碼。

(2) 初始化服務端和通道信息
ServerSocket server = new ServerSocket(8080);
    InetSocketAddress isa = new InetSocketAddress("localhost", 8080);
複製代碼

模擬開啓8080端口,而後配置套接字通道信息爲本地的8080端口。

(3) 開啓兩個通道,用兩個線程來掉用
SocketChannel sc1 = SocketChannel.open(isa);
    SocketChannel sc2 = SocketChannel.open(isa);

    Thread t1 = new Thread(new NIOBlockExample(sc2), "一號");
    t1.start();

    Thread t2 = new Thread(new NIOBlockExample(sc1), "二號");
    t2.start();
複製代碼
(4)調用interrupt
//打斷標誌
    t1.interrupt();
    t2.interrupt();
複製代碼

斷點打在if判斷條件上,咱們能夠看到blocker指向的是一個AbstractInterruptibleChannel抽象類對象,那麼咱們初步能夠判定,應該是AbstractInterruptibleChannel中的某個相似的set方法來完成對blocker對賦值工做的

因此咱們繼續往下追蹤到 java.nio.channels.spi.AbstractInterruptibleChannel,同窗們能夠經過

這段代碼來往下追蹤

老套路先看他一波API說明

//可中斷通道的基本實現類
/**
 * Base implementation class for interruptible channels.
 *
//方法在可能出現I/O阻塞的狀況下必須先調用begin,而後再調用end,而且是須要在一
//個try{}finally{}裏面來完成這些通道的開啓和關閉
 * <p> This class encapsulates the low-level machinery required to implement
 * the asynchronous closing and interruption of channels.  A concrete channel
 * class must invoke the {@link #begin begin} and {@link #end end} methods
 * before and after, respectively, invoking an I/O operation that might block
 * indefinitely.  In order to ensure that the {@link #end end} method is always
 * invoked, these methods should be used within a
 * <tt>try</tt>&nbsp;...&nbsp;<tt>finally</tt> block:
複製代碼

這裏咱們瞭解到這個抽象類是一個可中斷通道的基本實現類,而且API說明裏面也給咱們提供了調用的代碼模版

* boolean completed = false;
 * try {
 *     begin();
 *     completed = ...;    // 執行阻塞I/O操做
 *     return ...;         // 返回結果
 * } finally {
 *     end(completed);
複製代碼

在這裏咱們能夠看到以前咱們的SocketChannel.read對應這邊的應該是執行阻塞I/O操做,也就是說,在咱們中斷方法以前,該類已經調用了begin方法了。同窗們這裏可能會感受很奇怪,他是怎麼作到的。咱們來看下SocketChannel的繼承關係就一目瞭然了

public abstract class SocketChannel
    extends AbstractSelectableChannel
    implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel
複製代碼

能夠看到SocketChannel繼承了AbstractSelectableChannel抽象類,這樣一切都瓜熟蒂落了。

接下來咱們再來看下begin究竟是何方神聖

/**
    //標記一個可能發生I/O阻塞的操做的開始
     * Marks the beginning of an I/O operation that might block indefinitely.
     *
     //該方法須要和end方法同步調用,爲了實現異步阻塞通道的關閉和中斷
     * <p> This method should be invoked in tandem with the {@link #end end}
     * method, using a <tt>try</tt>&nbsp;...&nbsp;<tt>finally</tt> block as
     * shown <a href="#be">above</a>, in order to implement asynchronous
     * closing and interruption for this channel.  </p>
     */
    protected final void begin() {
        if (interruptor == null) {
            interruptor = new Interruptible() {
                    public void interrupt(Thread target) {
                        synchronized (closeLock) {
                            if (!open)
                                return;
                            open = false;
                            interrupted = target;
                            try {
                                AbstractInterruptibleChannel.this.implCloseChannel();
                            } catch (IOException x) { }
                        }
                    }};
        }
        blockedOn(interruptor);
        Thread me = Thread.currentThread();
        if (me.isInterrupted())
            interruptor.interrupt(me);
    }
複製代碼

斷點打在同步塊入口處

此時咱們肯定 interrupt甚至尚未開始打標誌,可是這時候咱們發現 blocker已經被賦值了,這裏就證實了,在阻塞I/O操做以前會初始化 blocker狀態。也就是說在尚未調用 interrupt以前就已經調用了 begin。咱們這時候斷點打在begin,一探究竟。

同窗們注意看我紅色方框標註出來的地方,咱們在 begin入口處打斷點,發現這邊發掉調用的是咱們最初的 open方法,而且咱們也看到這邊發起調用的是咱們的主程序 main函數

因此咱們能夠得出結論: 在open函數調用的時候會初始化一個new Interruptible()對象,而且是由parent Thread 來建立的。

⚠️這裏有個巨坑

當咱們把斷點打在begin以後,debug的時候會發現和咱們直接run的結果徹底不一樣,直接上圖你們看下

debug狀況下的結果:

Connected to the target VM, address: '127.0.0.1:52529', transport: 'socket'
Exception in thread "main" java.nio.channels.ClosedByInterruptException
	at java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:202)
	at sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:659)
	at java.nio.channels.SocketChannel.open(SocketChannel.java:189)
	at src.com.lyf.page3.InterruptNIO.main(InterruptNIO.java:50)
Disconnected from the target VM, address: '127.0.0.1:52529', transport: 'socket'
複製代碼

run狀況下的結果:

Waiting for read() in src.com.lyf.page3.NIOBlockExample@168b9408
Waiting for read() in src.com.lyf.page3.NIOBlockExample@740759f
一號你炸啦!!!!!!!!!!!!
二號你炸啦!!!!!!!!!!!!
java.nio.channels.ClosedByInterruptException
	at java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:202)
	at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:407)
	at src.com.lyf.page3.NIOBlockExample.run(NIOBlockExample.java:26)
	at java.lang.Thread.run(Thread.java:748)
一號 = ClosedByInterruptException
java.nio.channels.ClosedByInterruptException
一號 = Exiting NIOBlocked.run() src.com.lyf.page3.NIOBlockExample@168b9408
	at java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:202)
	at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:407)
	at src.com.lyf.page3.NIOBlockExample.run(NIOBlockExample.java:26)
	at java.lang.Thread.run(Thread.java:748)
二號 = ClosedByInterruptException
二號 = Exiting NIOBlocked.run() src.com.lyf.page3.NIOBlockExample@740759f
複製代碼

爲何會這樣呢?爲何呢?我也是調試來好久,最後發現若是斷點打在begin,會致使main線程被阻塞,從而致使socket通道關閉。也就是說咱們的斷點是不能阻斷main線程的運行的。光說結論沒有事實可不行,這裏咱們改造一下咱們的代碼。

ServerSocket server = new ServerSocket(8080);
    InetSocketAddress isa = new InetSocketAddress("localhost", 8080);
    SocketChannel sc1 = null;
    SocketChannel sc2 = null;
    try {
      sc1 = SocketChannel.open(isa);
      sc2 = SocketChannel.open(isa);
    } catch (ClosedByInterruptException e) {

    }

    Thread t1 = new Thread(new NIOBlockExample(sc2), "一號");
    t1.start();

    Thread t2 = new Thread(new NIOBlockExample(sc1), "二號");
    t2.start();
    //打斷標誌
    t1.interrupt();
    t2.interrupt();
複製代碼

open方法無恥地try起來,這時候咱們再次debug就能看到結果以下:

同時咱們把 begin不打斷點打結果圖拿來對比着看

同窗們這時候應該已經能夠確信咱們上面得出的結論了吧,也就是 若是斷點打在begin,會致使main線程被阻塞,從而致使socket通道關閉,這裏咱們能夠清楚地看到 sc1sc2都爲null。

好的巨坑已經給同窗們填好了終於能夠放心地debug了😄


咱們如今再回到咱們的begin來說,在open的時候幫咱們初始化了對象以後,緊接着他調用了blockedOn(interruptor);,鑑於不能把main阻塞的原則咱們在斷點上添加過濾條件!"main".equals(Thread.currentThread().getName()),繼續在blockedOn(interruptor);處打斷點,咱們能夠看到一號線程和二號線程都會進來。

終於咱們能夠肯定是 blockedOn函數幫助咱們完成了 private volatile Interruptible blocker;的賦值,關於具體這麼實現的,這裏咱們再也不深刻探究,感興趣的同窗能夠自行查看System內部相關代碼。

咱們既然講了begin,順便咱們把java.nio.channels.spi.AbstractInterruptibleChannel#end也講一講。

/**
    標誌I/O操做的結束
     * Marks the end of an I/O operation that might block indefinitely.
     *
     //確保begin和end一塊兒調用
     * <p> This method should be invoked in tandem with the {@link #begin
     * begin} method, using a <tt>try</tt>&nbsp;...&nbsp;<tt>finally</tt> block
     * as shown <a href="#be">above</a>, in order to implement asynchronous
     * closing and interruption for this channel.  </p>
     *
     * @param  completed
     *         <tt>true</tt> if, and only if, the I/O operation completed
     *         successfully, that is, had some effect that would be visible to
     *         the operation's invoker * * @throws AsynchronousCloseException * If the channel was asynchronously closed * * @throws ClosedByInterruptException * If the thread blocked in the I/O operation was interrupted */ protected final void end(boolean completed) throws AsynchronousCloseException { blockedOn(null); Thread interrupted = this.interrupted; if (interrupted != null && interrupted == Thread.currentThread()) { interrupted = null; throw new ClosedByInterruptException(); } if (!completed && !open) throw new AsynchronousCloseException(); } 複製代碼

這邊給咱們提供了兩種異常處理

1.AsynchronousCloseException 只有在通道被異步關閉的時候會拋出這個異常。

2.ClosedByInterruptException 線程由於I/O操做而致使Blocked的狀況下會拋出這個異常。

結合咱們以前main打斷點的而致使通道被關閉拋出ClosedByInterruptException異常可知咱們的斷點實際上是會致使main被blocked中斷了致使的I/O異常。


關於interruptAPI咱們也研究地差很少了,這裏問同窗們一個問題,咱們一旦調用interrupt以後,線程就會立馬被中斷嗎?

答案是不會的,咱們來看個例子

private static class MySleep implements Runnable {

    @Override
    public void run() {
      try {
        TimeUnit.SECONDS.sleep(10);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      for (int i = 0; i < 10; i++) {
        System.out.println("我一直都在");
      }
    }
  }

  public static void main(String[] args) {

    Thread thread = new Thread(new MySleep(), "我是個沒有感情的標籤");
    thread.start();
    System.out.println("我開始打斷你");
    thread.interrupt();
    System.out.println("你已經被我打斷了");

  }
複製代碼

輸出:

我開始打斷你
你已經被我打斷了
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at src.com.lyf.page3.InterruptIsJustSign$MySleep.run(InterruptIsJustSign.java:17)
	at java.lang.Thread.run(Thread.java:748)
我一直都在
我一直都在
我一直都在
我一直都在
我一直都在
我一直都在
我一直都在
我一直都在
我一直都在
我一直都在
複製代碼

咱們能夠看到即便被中斷了sleep線程仍是會把剩下的邏輯執行完成,也就是說interrupt是一個標誌,線程自己是否是中斷中止仍是要看線程自己是否結束。咱們的interrupt只做用於咱們以前列出的那幾類方法。

到這裏咱們的interrupt大體就介紹完了

isInterrupted

老套路

/**
    //查看線程有沒有被中斷,此方法不會影響線程的中斷狀態
     * Tests whether this thread has been interrupted.  The <i>interrupted
     * status</i> of the thread is unaffected by this method.
     *
     //線程中斷被忽略的時候此方法一直返回false
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if this thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see     #interrupted()
     * @revised 6.0
     */
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
複製代碼

這個方法其實就是Thread的一個成員方法,相對來講比較簡單,可是再簡單咱們也要看下究竟是怎麼個用法,下面給出對應的使用案例。

private static class MyInterrupted implements Runnable {

    @Override
    public void run() {
      System.out.println("我已經被啓動了");
      try {
        TimeUnit.SECONDS.sleep(10);
      } catch (InterruptedException e) {
        System.out.println("我被中斷了");
      }
    }
  }

  public static void main(String[] args) {
    Thread thread = new Thread(new MyInterrupted(), "一號");
    System.out.println("線程未啓動時候中斷標誌:" + thread.isInterrupted());
    thread.start();
    System.out.println("線程啓動時候中斷標誌:" + thread.isInterrupted());
    thread.interrupt();
    System.out.println("線程啓動並調用interrupt時候中斷標誌:" + thread.isInterrupted());
    
  }
複製代碼

輸出:

線程未啓動時候中斷標誌:false
線程啓動時候中斷標誌:false
線程啓動並調用interrupt時候中斷標誌:true
我已經被啓動了
我被中斷了
複製代碼

interrupted

/**
    測試當前線程是否已經被中斷,這個方法會清除中斷標誌,也就是第一次調用是
    中斷以後第一次調用會返回true,除非再次中斷否則返回都是false
     * Tests whether the current thread has been interrupted.  The
     * <i>interrupted status</i> of the thread is cleared by this method.  In
     * other words, if this method were to be called twice in succession, the
     * second call would return false (unless the current thread were
     * interrupted again, after the first call had cleared its interrupted
     * status and before the second call had examined it).
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if the current thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see #isInterrupted()
     * @revised 6.0
     */
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
複製代碼

isInterrupted的區別就是interrupted會清除中斷狀態,這個咱們用具體代碼來理解一下

public static void main(String[] args) {

    System.out.println("調用中斷方法以前isInterrupted()標記爲:" + Thread.currentThread().isInterrupted());
    System.out.println("調用中斷方法以前interrupted標記爲:" + Thread.interrupted());
    Thread.currentThread().interrupt();
    System.out.println("調用中斷方法以後isInterrupted()標記爲,第一次輸出:" + Thread.currentThread().isInterrupted());
    System.out.println("interrupted()標記爲,第一次輸出:" + Thread.interrupted());
    System.out.println("isInterrupted()標記爲,第二次輸出:" + Thread.currentThread().isInterrupted());
    System.out.println("interrupted()標記爲,第二次輸出:" + Thread.interrupted());
  }
複製代碼

輸出:

調用中斷方法以前isInterrupted()標記爲:false
調用中斷方法以前interrupted標記爲:false
調用中斷方法以後isInterrupted()標記爲,第一次輸出:true
interrupted()標記爲,第一次輸出:true
isInterrupted()標記爲,第二次輸出:false
interrupted()標記爲,第二次輸出:false
複製代碼

前面兩段很好理解,在沒有調用interrupt中斷線程以前,線程中斷狀態返回默認都是false,第三行和第四行也好理解表示調用中斷方法以後,線程的中斷狀態變爲true。咱們主要來看第五第六行,咱們發現調用了interrupted()以後,線程狀態編程了false,這也印證了咱們開頭說的interupted會清除中斷狀態


終於把這塊寫出來了,本章的重點都在調試interrupt這塊內容上,雖然比較繁瑣,可是同窗們若是本身也能親手跟着個人代碼一塊兒去探索一下的話相信會給你們帶來很大的提高。最後但願同窗們點點贊,點點關注呀~~🙏

相關文章
相關標籤/搜索