# Thread interrupt方法疑惑

概述

对于线程的正确停止而言,我们通常设置一个标志位,然后当另外一个线程可以通过修改该标志位,然后将线程停止。代码如下。

// 伪代码

boolean flag = true;

threadA()->{

 while(flag){

  // doSomething

 }

}



threadB()->{

 flag = false; // 此时将线程A停止

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

理想情况下,我们可以让线程停止,但是有时线程可能在执行阻塞操作,比如:在等待锁,那么这时光是设置标志位并没有什么用,我们需要将线程从阻塞状态中返回,这时我们可以通过Thread类的interrupt方法来中断线程。在Thread类中,对于中断的处理有如下几个方法:

// 中断当前线程

public void interrupt() {

 if (this != Thread.currentThread())

  checkAccess();



 synchronized (blockerLock) { // 这里我们不考虑该对象,该blocker对象用于表示当前线程阻塞在哪个对象上

  Interruptible b = blocker;

  if (b != null) {

   interrupt0();   // Just to set the interrupt flag

   b.interrupt(this);

   return;

  }

 }

 interrupt0(); // 直接看该方法

}



// 判断当前线程是否发生了中断,并清除中断标志位

public static boolean interrupted() {

 return currentThread().isInterrupted(true);

}



// 判断当前线程是否发生了中断,不清除中断标志位

public boolean isInterrupted() {

 return isInterrupted(false);

}



private native void interrupt0(); // JNI 方法完成中断

private native boolean isInterrupted(boolean ClearInterrupted); // JNI 方法判断是会否发生中断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

那么,这时在某些情况下会产生疑惑:什么时候我在调用 interrupt 方法时会抛出中断异常,什么时候又不会。比如以下代码。LockSupport.park()方法阻塞时,调用线程中断,那么将会导致线程被唤醒,将不会抛出异常,而对于Object.wait()来说,将会抛出异常。这是为何?本文将详细解释该现象出现的原因。

public class LockSupport {

 public static void park() { // 将当前线程阻塞(此时线程中断,仅仅简单唤醒,而不会抛出异常)

  UNSAFE.park(false, 0L);

 }

}



public class Object {

 // 将当前在synchronized块中的线程移动到等待池中,此时如果线程中断,将会抛出异常

 public final void wait() throws InterruptedException {

  wait(0);

 }

 public final native void wait(long timeout) throws InterruptedException;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

interrupt0方法原理

通过源码我们很容易得知,线程可能阻塞的结构如下:

  1. ParkEvent *_SleepEvent
  2. Parker *parker
  3. ParkEvent * _ParkEvent

该方法将通过osthread->set_interrupted(true)设置中断标志位后,如果以上三个阻塞结构存在,那么调用其 unpark() 方法完成唤醒,那么什么是 ParkEvent ?什么是Parker ?参考《深入理解java高并发编程》一书的最后一章,这里我们只需要知道它是两个阻塞结构即可。

JVM_ENTRY(void, JVM_Interrupt(JNIEnv* env, jobject jthread))

 oop java_thread = JNIHandles::resolve_non_null(jthread); // 获取java Thread对象

MutexLockerEx ml(thread->threadObj() == java_thread ? NULL : Threads_lock); // 获取线程锁保证在处理中断时线程安全

JavaThread* thr = java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)); // 获取C++层面的JavaThread对象(注意:该对象不是Java的 Thread,只有oop才是,这里为C++层面表示Thread的JavaThread C++类)

if (thr != NULL) {

 Thread::interrupt(thr); // 执行中断

}

JVM_END



 void Thread::interrupt(Thread* thread) {

  os::interrupt(thread); // 调用os模块处理中断

 }



void os::interrupt(Thread* thread) {

 OSThread* osthread = thread->osthread(); // 获取当前JavaThread对应的OSThread C++对象,该对象用于表示原生pthread 线程库(以Linux为例)创建的线程

 if (!osthread->interrupted()) { // 若之前线程未被中断,那么设置中断标志位,同时如果当前线程阻塞在_SleepEvent中,那么调用其unpark方法将其唤醒(注意:这里为核心,为什么中断能唤醒线程,原理在于此:中断标志位设置后将会把线程唤醒)

  osthread->set_interrupted(true);

  OrderAccess::fence();

  ParkEvent * const slp = thread->_SleepEvent ;

  if (slp != NULL) slp->unpark() ;

 }

 if (thread->is_Java_thread()) // 当前中断的线程为Thread java线程,那么也将Parker唤醒

  ((JavaThread*)thread)->parker()->unpark();

 ParkEvent * ev = thread->_ParkEvent ; // 线程如果拥有其他_ParkEvent,那么也将其唤醒

 if (ev != NULL) ev->unpark() ;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

IsInterrupted方法原理

JVM_QUICK_ENTRY(jboolean, JVM_IsInterrupted(JNIEnv* env, jobject jthread, jboolean clear_interrupted))

 // 获取Java Thread 线程,然后获取锁,获取C++ JavaThread对象,然后调用is_interrupted

 oop java_thread = JNIHandles::resolve_non_null(jthread);

 MutexLockerEx ml(thread->threadObj() == java_thread ? NULL : Threads_lock);

 JavaThread* thr = java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread));

 if (thr == NULL) {

 return JNI_FALSE;

 } else {

 return (jboolean) Thread::is_interrupted(thr, clear_interrupted != 0);

 }

JVM_END



// 直接调用OS模块来查看中断,clear_interrupted 用于标识当前是否应该清除中断标志位

bool Thread::is_interrupted(Thread* thread, bool clear_interrupted) {

 return os::is_interrupted(thread, clear_interrupted); 

}





// 获取OSThread 获取其interrupted标志位,如果指定清除,那么将其设置为false

bool os::is_interrupted(Thread* thread, bool clear_interrupted) {

 OSThread* osthread = thread->osthread();

 bool interrupted = osthread->interrupted();

 if (interrupted && clear_interrupted) {

 osthread->set_interrupted(false);

 }

 return interrupted;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

小结

由此可见,线程中断仅仅只是设置中断标志位,然后唤醒阻塞的线程,其他什么也不做,而判断中断是否已经发生,仅仅根据OSThread模块的interrupted中断标志位来判断,其他也什么都不做。

park 阻塞原理

要想了解为何不抛出中断异常,那么我们直接看park阻塞时,和被唤醒后的处理操作。通过源码我们看到Parker阻塞后,将不会进一步处理中断操作,而是直接返回,所以将不会抛出中断异常。

UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time))

 thread->parker()->park(isAbsolute != 0, time); // 直接调用Parker的park方法

UNSAFE_END

 

void Parker::park(bool isAbsolute, jlong time) { // isAbsolute表示是否为绝对时间,time表示阻塞时长

 if (Atomic::xchg(0, &_counter) > 0) return; // 以_counter值是否为0来判断当前线程是否已经执行过park方法(这里使用xchg,将会把0设置为_counter值,同时返回旧_counter值,判断是否大于0)

 Thread* thread = Thread::current();

 JavaThread *jt = (JavaThread *)thread;

 if (Thread::is_interrupted(thread, false)) { // 若此时线程已经调用过interrupt方法,那么直接返回

 return;

 }

 ...

 int status ;

 if (_counter > 0) { // 此时_counter已经被其他线程设置,那么将其设置为0并返回(也即调用过unpark,具体参考《深入理解java高并发编程》最后一章)

 _counter = 0;

 status = pthread_mutex_unlock(_mutex);

 OrderAccess::fence();

 return;

 }

 // 根据是否超时等待,来阻塞线程

 if (time == 0) {

 _cur_index = REL_INDEX;

 status = pthread_cond_wait (&_cond[_cur_index], _mutex) ; // 永久等待

 } else { // 超时等待

 _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;

 status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;

 if (status != 0 && WorkAroundNPTLTimedWaitHang) {

  pthread_cond_destroy (&_cond[_cur_index]) ;

  pthread_cond_init (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr());

 }

 }

 _counter = 0 ; // 被唤醒后清除_counter,然后返回

 ...

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

wait 阻塞原理

要想了解为何抛出中断异常,那么我们直接看wait 阻塞时,和被唤醒后的处理操作。通过源码,我们很自然看到,当线程在被中断唤醒时,由于WasNotified为false,那么此时将会抛出InterruptedException异常。

JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))

 Handle obj(THREAD, JNIHandles::resolve_non_null(handle)); // 包装锁对象

 ...

 ObjectSynchronizer::wait(obj, ms, CHECK); // 调用同步器wait方法

JVM_END

 

// 将锁膨胀为重量级锁,因为只有重量级锁才会存在wait集合来放置线程(详细参考《深入理解java高并发编程》)

void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {

 ... 

 ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj()); 

 monitor->wait(millis, true, THREAD); // 通过监视器等待,这里指定true,表示可以通过线程中断唤醒

}



void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {

 Thread * const Self = THREAD ; // 获取当前JavaThread对象

 ObjectWaiter node(Self); // 封装等待节点

 node.TState = ObjectWaiter::TS_WAIT ; 

 AddWaiter (&node) ; // 将节点插入_WaitSet等待池中

 ...

 exit (true, Self) ; // 退出监视器,释放锁

 if (interruptible && (Thread::is_interrupted(THREAD, false) || HAS_PENDING_EXCEPTION)) { // 当前线程已经被中断,同时指定了interruptible为true,表示可以响应中断(前面我们进入该方法时设置为true),或者当前线程有未处理的异常(HAS_PENDING_EXCEPTION),那么不阻塞

 } 

 else if (node._notified == 0) { // 否则阻塞在线程的_ParkEvent上(还记得中断会操作该对象唤醒么)

  if (millis <= 0) {

   Self->_ParkEvent->park () ;

  } else {

   ret = Self->_ParkEvent->park (millis) ;

  }

 }

 ...

 WasNotified = node._notified ; // 判断当前线程是否是被正常的Object.notify方法唤醒,注意:当被中断唤醒时将不会设置该标志位

 ...

 if (!WasNotified) { // 抛出中断异常

 if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) {

  TEVENT (Wait - throw IEX from epilog) ;

  THROW(vmSymbols::java_lang_InterruptedException());

 }

 }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

sleep 阻塞原理

我们再来看看Thread.sleep时,为何也会抛出InterruptedException异常。通过源码我们得知:线程将会阻塞在_SleepEvent上,该对象将会在Thread.interrupt时调用其unpark唤醒,这时将会在这里检测中断标志位返回OS_INTRPT,将导致线程抛出中断异常。

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

 if (millis == 0) { // 超时时间为0,尝试转为yield让出CPU,该ConvertSleepToYield默认为true

 if (ConvertSleepToYield) { 

  os::yield();

 } else { // 否则执行睡眠操作

  ThreadState old_state = thread->osthread()->get_state();

  thread->osthread()->set_state(SLEEPING);

  os::sleep(thread, MinSleepInterval, false);

  thread->osthread()->set_state(old_state);

 }

 } else { // 直接执行睡眠操作

 ThreadState old_state = thread->osthread()->get_state();

 thread->osthread()->set_state(SLEEPING);

 if (os::sleep(thread, millis, true) == OS_INTRPT) { // 因为中断被唤醒,那么抛出异常

 ...

  THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");

 }

JVM_END

 

int os::sleep(Thread* thread, jlong millis, bool interruptible) {

 ParkEvent * const slp = thread->_SleepEvent ; // 获取_SleepEvent对象

 slp->reset() ;

 OrderAccess::fence() ;



 if (interruptible) { // 可以被中断唤醒

 jlong prevtime = javaTimeNanos();

 for (;;) {

  if (os::is_interrupted(thread, true)) { // 当前线程已经被中断

  return OS_INTRPT;

 }

  jlong newtime = javaTimeNanos();



  if (newtime - prevtime < 0) {

  // time moving backwards, should only happen if no monotonic clock

  // not a guarantee() because JVM should not abort on kernel/glibc bugs

  assert(!Linux::supports_monotonic_clock(), "time moving backwards");

 } else {

  millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;

 }



  if(millis <= 0) { // 已经超时,那么直接正常返回

  return OS_OK;

 }

  prevtime = newtime;



 {

  ...

  slp->park(millis); // 开始睡眠

  ...

 }

 }

 } else { // 不响应中断唤醒,可以看到这里没有os::is_interrupted(thread, true)代码

 for (;;) {

  jlong newtime = javaTimeNanos();

  if (newtime - prevtime < 0) {

  // time moving backwards, should only happen if no monotonic clock

  // not a guarantee() because JVM should not abort on kernel/glibc bugs

  assert(!Linux::supports_monotonic_clock(), "time moving backwards");

 } else {

  millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;

 }

  if(millis <= 0) break ;

  prevtime = newtime;

  slp->park(millis);

 }

 return OS_OK ;

 }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

THROW 原理

接下来我们来看看JVM如何抛出异常。通过源码我们得知:两个宏定义最终调用_throw方法完成异常处理,然后获取当前Thread 设置 pending_exception ,这时将会在线程执行字节码时进行检测,当发现有挂起未处理的异常时,进行处理。

#define THROW(name) { Exceptions::_throw_msg(THREAD_AND_LOCATION, name, NULL); return; }



#define THROW_MSG(name, message) { Exceptions::_throw_msg(THREAD_AND_LOCATION, name, message); return; }



void Exceptions::_throw_msg(Thread* thread, const char* file, int line, Symbol* name, const char* message,Handle h_loader, Handle h_protection_domain) {

 ...

 _throw(thread, file, line, h_exception, message);

}





void Exceptions::_throw(Thread* thread, const char* file, int line, Handle h_exception, const char* message) {

 ...

 thread->set_pending_exception(h_exception(), file, line);

 ...

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

总结

线程在中断时何时抛出异常,体现在代码中如何处理,也即中断后,是否判断中断异常标志位。而对于Unsafe.park来说,它将不会响应中断标志位,所以中断后仅仅将其唤醒,取决于设计。