本文主要内容

  1. 讲解 3 种让线程等待和唤醒的方法,每种方法配合具体的示例
  2. 介绍 LockSupport 主要用法
  3. 对比 3 种方式,了解他们之间的区别

LockSupport位于java.util.concurrent简称 juc)包中,算是 juc 中一个基础类,juc 中很多地方都会使用 LockSupport,非常重要,希望大家一定要掌握。
关于线程等待/唤醒的方法,前面的文章中我们已经讲过 2 种了:

  1. 方式 1:使用 Object 中的 wait()方法让线程等待,使用 Object 中的 notify()方法唤醒线程
  2. 方式 2:使用 juc 包中 Condition 的 await()方法让线程等待,使用 signal()方法唤醒线程

这 2 种方式,我们先来看一下示例。

使用 Object 类中的方法实现线程等待和唤醒

示例 1:

package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;

/
微信公众号:程序员路人,专注于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注!
/
public class Demo1** {

  1. **static** Object lock = **new** Object();
  2. **public** **static** **void** **main**(String[] args) **throws** InterruptedException {<br /> Thread t1 = **new** Thread(() -> {<br /> **synchronized** (lock) {<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");<br /> **try** {<br /> lock.wait();<br /> } **catch** (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");<br /> }<br /> });<br /> t1.setName("t1");<br /> t1.start();<br /> //休眠5秒<br /> TimeUnit.SECONDS.sleep(5);<br /> **synchronized** (lock) {<br /> lock.notify();<br /> }<br /> }<br />}

输出:
1563592938744,t1 start!
1563592943745,t1 被唤醒!

t1 线程中调用lock.wait()方法让 t1 线程等待,主线程中休眠 5 秒之后,调用lock.notify()方法唤醒了 t1 线程,输出的结果中,两行结果相差 5 秒左右,程序正常退出。

示例 2

我们把上面代码中 main 方法内部改一下,删除了synchronized关键字,看看有什么效果:
package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;

/
微信公众号:程序员路人,专注于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注!
/
public class Demo2** {

  1. **static** Object lock = **new** Object();
  2. **public** **static** **void** **main**(String[] args) **throws** InterruptedException {<br /> Thread t1 = **new** Thread(() -> {<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");<br /> **try** {<br /> lock.wait();<br /> } **catch** (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");<br /> });<br /> t1.setName("t1");<br /> t1.start();<br /> //休眠5秒<br /> TimeUnit.SECONDS.sleep(5);<br /> lock.notify();<br /> }<br />}

运行结果:
Exception in thread “t1” java.lang.IllegalMonitorStateException
1563593178811,t1 start!
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.itsoku.chat10.Demo2.lambda$main$0(Demo2.java:16)
at java.lang.Thread.run(Thread.java:745)
Exception in thread “main” java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.itsoku.chat10.Demo2.main(Demo2.java:26)

上面代码中将synchronized去掉了,发现调用 wait()方法和调用 notify()方法都抛出了IllegalMonitorStateException异常,原因:Object 类中的 wait、notify、notifyAll 用于线程等待和唤醒的方法,都必须在同步代码中运行(必须用到关键字 synchronized)

示例 3

唤醒方法在等待方法之前执行,线程能够被唤醒么?代码如下:
package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;

/
微信公众号:程序员路人,专注于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注!
/
public class Demo3** {

  1. **static** Object lock = **new** Object();
  2. **public** **static** **void** **main**(String[] args) **throws** InterruptedException {<br /> Thread t1 = **new** Thread(() -> {<br /> **try** {<br /> TimeUnit.SECONDS.sleep(5);<br /> } **catch** (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> **synchronized** (lock) {<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");<br /> **try** {<br /> //休眠3秒<br /> lock.wait();<br /> } **catch** (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");<br /> }<br /> });<br /> t1.setName("t1");<br /> t1.start();<br /> //休眠1秒之后唤醒lock对象上等待的线程<br /> TimeUnit.SECONDS.sleep(1);<br /> **synchronized** (lock) {<br /> lock.notify();<br /> }<br /> System.out.println("lock.notify()执行完毕");<br /> }<br />}

运行代码,输出结果:
lock.notify()执行完毕
1563593869797,t1 start!

输出了上面 2 行之后,程序一直无法结束,t1 线程调用 wait()方法之后无法被唤醒了,从输出中可见,notify()方法在wait()方法之前执行了,等待的线程无法被唤醒了。说明:唤醒方法在等待方法之前执行,线程无法被唤醒。
关于 Object 类中的用户线程等待和唤醒的方法,总结一下:

  1. wait()/notify()/notifyAll()方法都必须放在同步代码(必须在 synchronized 内部执行)中执行,需要先获取锁
  2. 线程唤醒的方法(notify、notifyAll)需要在等待的方法(wait)之后执行,等待中的线程才可能会被唤醒,否则无法唤醒

    使用 Condition 实现线程的等待和唤醒

    Condition 的使用,前面的文章讲过,对这块不熟悉的可以移步JUC 中 Condition 的使用,关于 Condition 我们准备了 3 个示例。

    示例 1

    package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/
微信公众号:程序员路人,专注于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注!
/
public class Demo4** {

  1. **static** ReentrantLock lock = **new** ReentrantLock();<br /> **static** Condition condition = lock.newCondition();
  2. **public** **static** **void** **main**(String[] args) **throws** InterruptedException {<br /> Thread t1 = **new** Thread(() -> {<br /> lock.lock();<br /> **try** {<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");<br /> **try** {<br /> condition.await();<br /> } **catch** (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");<br /> } **finally** {<br /> lock.unlock();<br /> }<br /> });<br /> t1.setName("t1");<br /> t1.start();<br /> //休眠5秒<br /> TimeUnit.SECONDS.sleep(5);<br /> lock.lock();<br /> **try** {<br /> condition.signal();<br /> } **finally** {<br /> lock.unlock();<br /> }
  3. }<br />}

输出:
1563594349632,t1 start!
1563594354634,t1 被唤醒!

t1 线程启动之后调用condition.await()方法将线程处于等待中,主线程休眠 5 秒之后调用condition.signal()方法将 t1 线程唤醒成功,输出结果中 2 个时间戳相差 5 秒。

示例 2

我们将上面代码中的 lock.lock()、lock.unlock()去掉,看看会发生什么。代码:
package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/
微信公众号:程序员路人,专注于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注!
/
public class Demo5** {

  1. **static** ReentrantLock lock = **new** ReentrantLock();<br /> **static** Condition condition = lock.newCondition();
  2. **public** **static** **void** **main**(String[] args) **throws** InterruptedException {<br /> Thread t1 = **new** Thread(() -> {<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");<br /> **try** {<br /> condition.await();<br /> } **catch** (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");<br /> });<br /> t1.setName("t1");<br /> t1.start();<br /> //休眠5秒<br /> TimeUnit.SECONDS.sleep(5);<br /> condition.signal();<br /> }<br />}

输出:
Exception in thread “t1” java.lang.IllegalMonitorStateException
1563594654865,t1 start!
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036)
at com.itsoku.chat10.Demo5.lambda$main$0(Demo5.java:19)
at java.lang.Thread.run(Thread.java:745)
Exception in thread “main” java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
at com.itsoku.chat10.Demo5.main(Demo5.java:29)

有异常发生,condition.await();和condition.signal();都触发了IllegalMonitorStateException异常。原因:调用 condition 中线程等待和唤醒的方法的前提是必须要先获取 lock 的锁

示例 3

唤醒代码在等待之前执行,线程能够被唤醒么?代码如下:
package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/
微信公众号:程序员路人,专注于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注!
/
public class Demo6** {

  1. **static** ReentrantLock lock = **new** ReentrantLock();<br /> **static** Condition condition = lock.newCondition();
  2. **public** **static** **void** **main**(String[] args) **throws** InterruptedException {<br /> Thread t1 = **new** Thread(() -> {<br /> **try** {<br /> TimeUnit.SECONDS.sleep(5);<br /> } **catch** (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> lock.lock();<br /> **try** {<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");<br /> **try** {<br /> condition.await();<br /> } **catch** (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");<br /> } **finally** {<br /> lock.unlock();<br /> }<br /> });<br /> t1.setName("t1");<br /> t1.start();<br /> //休眠5秒<br /> TimeUnit.SECONDS.sleep(1);<br /> lock.lock();<br /> **try** {<br /> condition.signal();<br /> } **finally** {<br /> lock.unlock();<br /> }<br /> System.out.println(System.currentTimeMillis() + ",condition.signal();执行完毕");<br /> }<br />}

运行结果:
1563594886532,condition.signal();执行完毕
1563594890532,t1 start!

输出上面 2 行之后,程序无法结束,代码结合输出可以看出 signal()方法在 await()方法之前执行的,最终 t1 线程无法被唤醒,导致程序无法结束。
关于 Condition 中方法使用总结:

  1. 使用 Condtion 中的线程等待和唤醒方法之前,需要先获取锁。否则会报IllegalMonitorStateException异常
  2. signal()方法先于 await()方法之前调用,线程无法被唤醒

    Object 和 Condition 的局限性

    关于 Object 和 Condtion 中线程等待和唤醒的局限性,有以下几点:

  3. 2 种方式中的让线程等待和唤醒的方法能够执行的先决条件是:线程需要先获取锁

  4. 唤醒方法需要在等待方法之后调用,线程才能够被唤醒

关于这 2 点,LockSupport 都不需要,就能实现线程的等待和唤醒。下面我们来说一下 LockSupport 类。

LockSupport 类介绍

LockSupport 类可以阻塞当前线程以及唤醒指定被阻塞的线程。主要是通过park()unpark(thread)方法来实现阻塞和唤醒线程的操作的。
每个线程都有一个许可(permit),permit 只有两个值 1 和 0,默认是 0。

  1. 当调用 unpark(thread)方法,就会将 thread 线程的许可 permit 设置成 1(注意多次调用 unpark 方法,不会累加,permit 值还是 1)。
  2. 当调用 park()方法,如果当前线程的 permit 是 1,那么将 permit 设置为 0,并立即返回。如果当前线程的 permit 是 0,那么当前线程就会阻塞,直到别的线程将当前线程的 permit 设置为 1 时,park 方法会被唤醒,然后会将 permit 再次设置为 0,并返回。

注意:因为 permit 默认是 0,所以一开始调用 park()方法,线程必定会被阻塞。调用 unpark(thread)方法后,会自动唤醒 thread 线程,即 park 方法立即返回。

LockSupport 中常用的方法

阻塞线程

  • void park():阻塞当前线程,如果调用unpark 方法或者当前线程被中断,才能从 park()方法中返回
  • void park(Object blocker):功能同方法 1,入参增加一个 Object 对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查
  • void parkNanos(long nanos):阻塞当前线程,最长不超过 nanos 纳秒,增加了超时返回的特性
  • void parkNanos(Object blocker, long nanos):功能同方法 3,入参增加一个 Object 对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查
  • void parkUntil(long deadline):阻塞当前线程,直到 deadline,deadline 是一个绝对时间,表示某个时间的毫秒格式
  • void parkUntil(Object blocker, long deadline):功能同方法 5,入参增加一个 Object 对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查;

唤醒线程

  • void unpark(Thread thread):唤醒处于阻塞状态的指定线程

    示例 1

    主线程线程等待 5 秒之后,唤醒 t1 线程,代码如下:
    package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

/
微信公众号:程序员路人,专注于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注!
/
public class Demo7** {

  1. **public** **static** **void** **main**(String[] args) **throws** InterruptedException {<br /> Thread t1 = **new** Thread(() -> {<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");<br /> LockSupport.park();<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");<br /> });<br /> t1.setName("t1");<br /> t1.start();<br /> //休眠5秒<br /> TimeUnit.SECONDS.sleep(5);<br /> LockSupport.unpark(t1);<br /> System.out.println(System.currentTimeMillis() + ",LockSupport.unpark();执行完毕");<br /> }<br />}

输出:
1563597664321,t1 start!
1563597669323,LockSupport.unpark();执行完毕
1563597669323,t1 被唤醒!

t1 中调用LockSupport.park();让当前线程 t1 等待,主线程休眠了 5 秒之后,调用LockSupport.unpark(t1);将 t1 线程唤醒,输出结果中 1、3 行结果相差 5 秒左右,说明 t1 线程等待 5 秒之后,被唤醒了。
LockSupport.park();无参数,内部直接会让当前线程处于等待中;unpark 方法传递了一个线程对象作为参数,表示将对应的线程唤醒。

示例 2

唤醒方法放在等待方法之前执行,看一下线程是否能够被唤醒呢?代码如下:
package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/
微信公众号:程序员路人,专注于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注!
/
public class Demo8** {

  1. **public** **static** **void** **main**(String[] args) **throws** InterruptedException {<br /> Thread t1 = **new** Thread(() -> {<br /> **try** {<br /> TimeUnit.SECONDS.sleep(5);<br /> } **catch** (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");<br /> LockSupport.park();<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");<br /> });<br /> t1.setName("t1");<br /> t1.start();<br /> //休眠1秒<br /> TimeUnit.SECONDS.sleep(1);<br /> LockSupport.unpark(t1);<br /> System.out.println(System.currentTimeMillis() + ",LockSupport.unpark();执行完毕");<br /> }<br />}

输出:
1563597994295,LockSupport.unpark();执行完毕
1563597998296,t1 start!
1563597998296,t1 被唤醒!

代码中启动 t1 线程,t1 线程内部休眠了 5 秒,然后主线程休眠 1 秒之后,调用了LockSupport.unpark(t1);唤醒线程 t1,此时LockSupport.park();方法还未执行,说明唤醒方法在等待方法之前执行的;输出结果中 2、3 行结果时间一样,表示LockSupport.park();没有阻塞了,是立即返回的。
说明:唤醒方法在等待方法之前执行,线程也能够被唤醒,这点是另外 2 种方法无法做到的。Object 和 Condition 中的唤醒必须在等待之后调用,线程才能被唤醒。而 LockSupport 中,唤醒的方法不管是在等待之前还是在等待之后调用,线程都能够被唤醒。

示例 3

park()让线程等待之后,是否能够响应线程中断?代码如下:
package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/
微信公众号:程序员路人,专注于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注!
/
public class Demo9** {

  1. **public** **static** **void** **main**(String[] args) **throws** InterruptedException {<br /> Thread t1 = **new** Thread(() -> {<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");<br /> System.out.println(Thread.currentThread().getName() + ",park()之前中断标志:" + Thread.currentThread().isInterrupted());<br /> LockSupport.park();<br /> System.out.println(Thread.currentThread().getName() + ",park()之后中断标志:" + Thread.currentThread().isInterrupted());<br /> System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");<br /> });<br /> t1.setName("t1");<br /> t1.start();<br /> //休眠5秒<br /> TimeUnit.SECONDS.sleep(5);<br /> t1.interrupt();
  2. }<br />}

输出:
1563598536736,t1 start!
t1,park()之前中断标志:false
t1,park()之后中断标志:true
1563598541736,t1 被唤醒!

t1 线程中调用了 park()方法让线程等待,主线程休眠了 5 秒之后,调用t1.interrupt();给线程 t1 发送中断信号,然后线程 t1 从等待中被唤醒了,输出结果中的 1、4 行结果相差 5 秒左右,刚好是主线程休眠了 5 秒之后将 t1 唤醒了。结论:park 方法可以相应线程中断。
LockSupport.park 方法让线程等待之后,唤醒方式有 2 种:

  1. 调用 LockSupport.unpark 方法
  2. 调用等待线程的interrupt()方法,给等待的线程发送中断信号,可以唤醒线程

    示例 4

    LockSupport 有几个阻塞放有一个 blocker 参数,这个参数什么意思,上一个实例代码,大家一看就懂了:
    package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/
微信公众号:程序员路人,专注于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注!
/
public class Demo10** {

  1. **static** **class** **BlockerDemo** {<br /> }
  2. **public** **static** **void** **main**(String[] args) **throws** InterruptedException {<br /> Thread t1 = **new** Thread(() -> {<br /> LockSupport.park();<br /> });<br /> t1.setName("t1");<br /> t1.start();
  3. Thread t2 = **new** Thread(() -> {<br /> LockSupport.park(**new** BlockerDemo());<br /> });<br /> t2.setName("t2");<br /> t2.start();<br /> }<br />}

运行上面代码,然后用 jstack 查看一下线程的堆栈信息:
“t2” #13 prio=5 os_prio=0 tid=0x00000000293ea800 nid=0x91e0 waiting on condition [0x0000000029c3f000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007180bfeb0> (a com.itsoku.chat10.Demo10$BlockerDemo)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at com.itsoku.chat10.Demo10.lambda$main$1(Demo10.java:22)
at com.itsoku.chat10.Demo10$$Lambda$2/824909230.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)

“t1” #12 prio=5 os_prio=0 tid=0x00000000293ea000 nid=0x9d4 waiting on condition [0x0000000029b3f000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
at com.itsoku.chat10.Demo10.lambda$main$0(Demo10.java:16)
at com.itsoku.chat10.Demo10$$Lambda$1/1389133897.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)

代码中,线程 t1 和 t2 的不同点是,t2 中调用 park 方法传入了一个 BlockerDemo 对象,从上面的线程堆栈信息中,发现 t2 线程的堆栈信息中多了一行- parking to wait for <0x00000007180bfeb0> (a com.itsoku.chat10.Demo10$BlockerDemo),刚好是传入的 BlockerDemo 对象,park 传入的这个参数可以让我们在线程堆栈信息中方便排查问题,其他暂无他用。
LockSupport 的其他等待方法,包含有超时时间了,过了超时时间,等待方法会自动返回,让线程继续运行,这些方法在此就不提供示例了,有兴趣的朋友可以自己动动手,练一练。

线程等待和唤醒的 3 种方式做个对比

到目前为止,已经说了 3 种让线程等待和唤醒的方法了

  1. 方式 1:Object 中的 wait、notify、notifyAll 方法
  2. 方式 2:juc 中 Condition 接口提供的 await、signal、signalAll 方法
  3. 方式 3:juc 中的 LockSupport 提供的 park、unpark 方法

3 种方式对比:

|

Object Condtion LockSupport
前置条件 需要在 synchronized 中运行 需要先获取 Lock 的锁
无限等待 支持 支持 支持
超时等待 支持 支持 支持
等待到将来某个时间返回 不支持 支持 支持
等待状态中释放锁 会释放 会释放 不会释放
唤醒方法先于等待方法执行,能否唤醒线程 可以
是否能响应线程中断
线程中断是否会清除中断标志
是否支持等待状态中不响应中断 不支持 支持 不支持