interrupt()中斷對LockSupport.park()行為影響


原文摘自:https://www.jianshu.com/p/d48f854ead85

1.中斷后LockSupport.park()直接返回

public class MultInterruptParkDemo {
    public static volatile boolean flag = true;
    public static void main(String[] args) {
        ThreadDemo04 t4 = new ThreadDemo04();
        t4.start();
        t4.interrupt();
        flag = false;
    }
    public static class ThreadDemo04 extends Thread {
        @Override
        public void run() {
            while (flag) {
            }
            LockSupport.park();
            System.out.println("本打印出現在第一個park()之后");
            LockSupport.park();
            System.out.println("本打印出現在第二個park()之后");
        }
    }
}

結果:

本打印出現在第一個park()之后
本打印出現在第二個park()之后

1.1 分析

Thread中斷方法分析(包括HotSpot源碼)中interrupt源碼:

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()) {
    osthread->set_interrupted(true);

如果線程未設置中斷標志位,會進行設置。

LockSupport及HotSpot層Parker::park/unpark分析中park源碼:

void Parker::park(bool isAbsolute, jlong time) {
  // Ideally we'd do something useful while spinning, such
  // as calling unpackTime().

  // Optional fast-path check:
  // Return immediately if a permit is available.
  // We depend on Atomic::xchg() having full barrier semantics
  // since we are doing a lock-free update to _counter.
  if (Atomic::xchg(0, &_counter) > 0) return;

  Thread* thread = Thread::current();
  assert(thread->is_Java_thread(), "Must be JavaThread");
  JavaThread *jt = (JavaThread *)thread;

  // Optional optimization -- avoid state transitions if there's an interrupt pending.
  // Check interrupt before trying to wait
  if (Thread::is_interrupted(thread, false)) {
    return;
  }

只要線程設置了中斷標志位,就直接返回,且這里的Thread::is_interrupted(thread, false)因為是false,所以不會清除中斷標志位。

1.2 總結

  • t4.interrupt()會設置線程的中斷標志位
  • LockSupport.park()會檢查線程是否設置了中斷標志位,如果設置了,則返回(這里並不會清除中斷標志位)

2.unpark不會累積許可(最多為1)

public class MultInterruptParkDemo2 {
    public static volatile boolean flag = true;
    public static void main(String[] args) {
        ThreadDemo04 t4 = new ThreadDemo04();
        t4.start();
        LockSupport.unpark(t4);
        LockSupport.unpark(t4);
        LockSupport.unpark(t4);
        flag = false;
    }
    public static class ThreadDemo04 extends Thread {
        @Override
        public void run() {
            while (flag) {
            }
            LockSupport.park();
            System.out.println("本打印出現在第一個park()之后");
            LockSupport.park();
            System.out.println("本打印出現在第二個park()之后");
        }
    }
}

結果:

本打印出現在第一個park()之后

2.1 分析

LockSupport及HotSpot層Parker::park/unpark分析中park源碼:

void Parker::park(bool isAbsolute, jlong time) {
  // Ideally we'd do something useful while spinning, such
  // as calling unpackTime().

  // Optional fast-path check:
  // Return immediately if a permit is available.
  // We depend on Atomic::xchg() having full barrier semantics
  // since we are doing a lock-free update to _counter.
  if (Atomic::xchg(0, &_counter) > 0) return;

每次調用park都會將_counter直接置為0。

void Parker::unpark() {
  int s, status ;
  status = pthread_mutex_lock(_mutex);
  assert (status == 0, "invariant") ;
  s = _counter;
  _counter = 1;
每次調用unpark都會將_counter直接置為1.

2.2 總結

  • 因此,總的許可數總是保持在1,無論調用多少次unpark,都只會將_counter置為1。
  • 每次park都會將_counter置為0,如果之前為1,則直接返回。后面的park調用就會阻塞。

3.Thread.interrupted()會清除中斷標志

public class MultInterruptParkDemo3 {
    public static volatile boolean flag = true;
    public static void main(String[] args) {
        ThreadDemo04 t4 = new ThreadDemo04();
        t4.start();
        t4.interrupt();
        flag = false;
    }
    public static class ThreadDemo04 extends Thread {
        @Override
        public void run() {
            while (flag) {
            }
            LockSupport.park();
            System.out.println("本打印出現在第一個park()之后");
            System.out.println(Thread.interrupted());
            System.out.println(Thread.interrupted());
            LockSupport.park();
            System.out.println("本打印出現在第二個park()之后");
        }
    }
}

結果:

本打印出現在第一個park()之后
true
false

3.1 分析

Thread中斷方法分析(包括HotSpot源碼)中Thread.interrupted()源碼:

public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
bool os::is_interrupted(Thread* thread, bool clear_interrupted) {
  assert(Thread::current() == thread || Threads_lock->owned_by_self(),
    "possibility of dangling Thread pointer");

  OSThread* osthread = thread->osthread();

  bool interrupted = osthread->interrupted();

  if (interrupted && clear_interrupted) {
    osthread->set_interrupted(false);
    // consider thread->_SleepEvent->reset() ... optional optimization
  }

  return interrupted;
}

可以看到如果中斷了並且clear_interrupted為true,則會清除中斷標志位。

3.2 總結

Thread.interrupted()清除中斷標志位后,第二個LockSupport.park()就不會直接返回,而是正常地阻塞。

4.sleep清除中斷標志位但不會更改_counter

public class MultInterruptParkDemo4 {
    public static volatile boolean flag = true;
    public static void main(String[] args) {
        ThreadDemo04 t4 = new ThreadDemo04();
        t4.start();
        t4.interrupt(); // 1. 調用unpark()方法,重置counter為1
        flag = false;
    }
    public static class ThreadDemo04 extends Thread {
        @Override
        public void run() {
            while (flag) {
            }
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace(); // 2. 重置interrupt為false,LockSupport.park()將不會因為interrupt為true而放行!!
            }
            System.out.println("本打印出現在第一個sleep()之后");
            System.out.println(Thread.interrupted());
            System.out.println(Thread.interrupted());
            LockSupport.park(); // 3. counter為1,不發生阻塞;重置counter為0;
            System.out.println("本打印出現在第二個park()之后"); LockSupport.park(); // 4. interrupt為false,並且counter為0;阻塞在這里!!
       System.out.println("本打印出現在第三個park()之后");
} } }

結果

java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at wz.interrupt.MultInterruptParkDemo4$ThreadDemo04.run(MultInterruptParkDemo4.java:19)
本打印出現在第一個sleep()之后
false
false
本打印出現在第二個park()之后

4.1 分析

Thread中斷方法分析(包括HotSpot源碼)中interrupt源碼:

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()) {
    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() ;

}

會調用Parker::unpark(),那么_counter會設置為1.

參考阻塞線程的相關方法分析(包括HotSpot層源碼)中關於sleep源碼:

JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis))
  JVMWrapper("JVM_Sleep");

  if (millis < 0) {
    THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
  }

  if (Thread::is_interrupted (THREAD, true) && !HAS_PENDING_EXCEPTION) {
    THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
  }

如果線程已中斷,則清除中斷標記並拋出中斷異常,直接返回,但是並沒有更改_counter。

4.2 總結

  • interrupt會調用((JavaThread*)thread)->parker()->unpark(),將_counter設置為1,也即后面調用park不會阻塞
  • sleep如果檢測到中斷會直接清除中斷標志,並拋出異常。
  • 因此兩個Thread.interrupted()都返回false,且LockSupport.park()不會阻塞。

5.對於LockSupport.park()阻塞總結

如下兩個條件任何一個成立,park()都不會阻塞:

  • 中斷標志位存在(wait、join、sleep或Thread.interrupted()都會清除中斷標志位)
  • _counter為1(之前調用了unpark或者interrupt)

參考


作者:王偵
鏈接:https://www.jianshu.com/p/d48f854ead85
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM