LockSupport与线程中断
LockSupport与线程中断
1、线程中断机制
返回值 | 方法 | 注释 |
---|---|---|
void | interrupt() | 中断此线程 |
static boolean | interrupted() | 测试当前线程是否已被中断 |
boolean | isInterrupted() | 测试此线程是否已经被中断 |
- 三个方法了解过吗?用在哪?
- 如何停止一个运行中的线程?
- 如何中断一个运行中的线程?
1.1、什么是中断机制?
首先
- 一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。
其次
在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制——中断。
中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的
interrupt
方法,该方法也仅仅是将线程对象的中断标识设成true;接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,
此时究竟该做什么需要你自己写代码实现。
每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;
通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
eg.顾客在无烟餐厅中吸烟,服务员希望他别吸烟了,不是强行停止他吸烟,而是给他的标志位打为true,具体的停止吸烟还是要顾客自己停止。(体现了协商机制)
1.2、中断的相关API方法之三大方法说明
方法 | 含义 |
---|---|
public void interrupt() | 实例方法,实例方法interrupt()仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程 |
public static boolean interrupted() | 静态方法,Thread.interrupted();判断线程是否被中断,并清除当前中断状态这个方法做了两件事:1 返回当前线程的中断状态2 将当前线程的中断状态设为false(这个方法有点不好理解,因为连续调用两次的结果可能不一样。) |
public boolean isInterrupted() | 实例方法,判断当前线程是否被中断(通过检查中断标志位) |
1.3、大厂面试题:如何使用中断标识停止线程?
1.3.1、如何停止中断运行中的线程?
1.3.1.1、通过一个volatile变量实现
volatile保证了可见性,t2修改了标志位后能马上被t1看到
public class VolatileDemo { static volatile boolean isStop = false; public static void main(String[] args) { new Thread(() -> { while (true) { if (isStop) {//如果这个标志位被其他线程改为true了 System.out.println(Thread.currentThread().getName() + "\t isStop被修改为true,程序终止"); break; } System.out.println("t1 ------hello volatile");//----------------------如果没停止,那就一直打印 } }, "t1").start(); try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { isStop = true; }, "t2").start(); } }
1.3.1.2、通过AtomicBoolean(原子布尔型)
public class AtomicBooleanDemo {
static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
public static void main(String[] args) {
m1_volatile();
}
public static void m1_volatile() {
new Thread(() -> {
while (true) {
if (atomicBoolean.get()) {//如果这个标志位被其他线程改为true了
System.out.println(Thread.currentThread().getName() + "\t atomicBoolean被修改为true,程序终止");
break;
}
System.out.println("t1 ------hello atomicBoolean");//----------------------如果没停止,那就一直打印
}
}, "t1").start();
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
atomicBoolean.set(true);
}, "t2").start();
}
}
1.3.1.3、 通过Thread类自带的中断api方法实现
public class InterruptedDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {//一旦发现中断标志位被修改
System.out.println(Thread.currentThread().getName() + "\t isInterrupted()被修改为true,程序终止");
break;
}
System.out.println("t1 ------hello interrupt ");//----------------------如果没停止,那就一直打印
}
}, "t1");
t1.start();
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
t1.interrupt();//把t1中断
}, "t2").start();
// t1.interrupt();//也可以自己把自己(t1)中断
}
}
Interrupte—API源码分析
实例方法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();
}
- 进入interrupt0();
//Thread.java
/* Some private helper methods */
private native void setPriority0(int newPriority);
private native void stop0(Object o);
private native void suspend0();
private native void resume0();
private native void interrupt0(); //---------------------------调用了c底层
private native void setNativeName(String name);
- 实例方法isInterrupted,返回布尔值
//Thread.java
/**
* Tests whether this thread has been interrupted. The <i>interrupted
* status</i> of the thread is unaffected by this method.
*
* <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);
}
/**
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
private native boolean isInterrupted(boolean ClearInterrupted);//也调用了c底层
说明
具体来说,当对一个线程,调用 interrupt() 时:
如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态(中断状态将被清除),并抛出一个
InterruptedException
异常。(中断不活动的线程不会产生任何影响,看下面案例)
1.3.2、 当前线程的中断标识为true,是不是线程就立刻停止?
- 否
- 仅仅设置了一个中断状态
- 看看中断是否会立即停止这个300的线程
- 否,虽然中断标志位变了。但是i一直在循环
//如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以, **interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行**。
public class InterruptDemo02 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 300; i++) {
System.out.println("---------" + i);
}
System.out.println("after t1.interrupt()---第2次----" + Thread.currentThread().isInterrupted());//true
}, "t1");
t1.start();
System.out.println("before t1.interrupt()----" + t1.isInterrupted());//false
t1.interrupt();
try {
TimeUnit.MILLISECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after t1.interrupt()---第1次---" + t1.isInterrupted());//true
try {
TimeUnit.MILLISECONDS.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after t1.interrupt()---第3次---" + t1.isInterrupted());//false,原因:此时t1已经运行结束。中断不活动的线程不会产生任何影响
}
}
后手案例-深入
- 在我们基本中断程序的骨架上 + 一个sleep阻塞
- 中断异常 且 会导致程序无限循环.
//如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态(中断状态将被清除),并抛出一个`InterruptedException`异常。
public class InterruptDemo03 {
/**
* 1 中断标志位 默认是false
* <p>2 t2 ----->t1发出了中断协商,t2调用t1.interrupt(),中断标志位true
* <p>3 中断标志位true,正常情况下,程序停止,^-^
* <p> 4 中断标志位true,异常情况下,InterruptedException,将会把中断状态清除,并且将收到InterruptedException。中断标志位false导致无限循环。
* <p>
* 5 在catch块中,需要再次给中断标志位设置为true,2次调用停止
*/
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + "\t" +
"中断标志位:" + Thread.currentThread().isInterrupted() + "程序终止");
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt(); //假如加了这个,程序可以终止,只会爆异常。若不加,sleep方法抛出InterruptedException后,中断标识也被清空置为false,我们在catch没有通过th.interrupt()方法再次将中断标志设置为true,这就导致无限循环了
}
System.out.println("-----hello InterruptDemo03");
}
}, "t1");
t1.start();
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(t1::interrupt).start();
}
}

- // Thread.currentThread().interrupt(); 假如加了这个,程序可以终止,只会爆异常
为什么在异常处在调用异常interrupt()?
- 原因:
- 中断标志位 默认是false
- t2 ----->t1发出了中断协商,t2调用t1.interrupt(),中断标志位true
- 中断标志位true,正常情况下,程序停止,-
- 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的
interrupt
方法,那么线程将立即退出被阻塞状态(中断状态将被清除),并抛出一个InterruptedException
异常。 - 中断标志位true,异常情况下,将会把中断状态清除,并且将收到
InterruptedException
。sleep方法抛出InterruptedException
后,**中断标识也被清空置为false,**我们在catch没有通过interrupt()方法再次将中断标志设置为true,这就导致无限循环了 。
- 所以在catch块中,需要再次给中断标志位设置为true,2次调用停止
- 看下方interrupt方法官方 注释:then its interrupt status will be cleared and it will receive an
/**
* Interrupts this thread.
*
* <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.
*
* <p> If this thread is blocked in an invocation of the {@link
* Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
* Object#wait(long, int) wait(long, int)} methods of the {@link Object}
* class, or of the {@link #join()}, {@link #join(long)}, {@link
* #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
* methods of this class, then its interrupt status will be cleared and it
* will receive an {@link InterruptedException}.
*
* <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}.
*
* <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.
*
* <p> If none of the previous conditions hold then this thread's interrupt
* status will be set. </p>
*
* <p> Interrupting a thread that is not alive need not have any effect.
*
* @throws SecurityException
* if the current thread cannot modify this thread
*
* @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();
}
小总结
- 中断只是一种协同机制,修改中断标识位仅此而已,而不是立刻stop打断
1.3.3、静态方法Thread.interrupted()
api里的第二个
- public static boolean interrupted()
静态方法,Thread.interrupted();判断线程是否被中断,并清除当前中断状态这个方法做了两件事:
- 返回当前线程的中断状态
- 将当前线程的中断状态设为false(这个方法有点不好理解,因为连续调用两次的结果可能不一样。
public class InterruptedDemo {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//main false
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//main false
System.out.println("-----1");//-----1
Thread.currentThread().interrupt();//中断标志位设置为true
System.out.println("-----2");//-----2
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//main true
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//main false
}
}
看下源码,interrupted()
对比isInterrupted()
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
/**
* Tests whether this thread has been interrupted. The <i>interrupted
* status</i> of the thread is unaffected by this method.
*
* <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);
}
/**
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
private native boolean isInterrupted(boolean ClearInterrupted);
他们在底层都调用了
native
方法isInterrupted
。只不过传入参数
ClearInterrupted
一个传参传了true,一个传了false。静态方法
interrupted()
中true表示清空当前中断状态。实例方法
isInterrupted
则不会。
2、LockSupport
官方解释:用于创建锁和其他同步类的基本线程阻塞原语。
核心就是park()和unpark()方法
- park()方法是阻塞线程
- unpark()方法是解除阻塞线程
文档参考:https://www.runoob.com/manual/jdk11api/java.base/java/util/concurrent/locks/LockSupport.html
变量和类型 | 方法 | 描述 |
---|---|---|
static Object | getBlocker(Thread t) | 返回提供给尚未解除阻塞的park方法的最新调用的阻止程序对象,如果未阻止,则返回null。 |
static void | park() | 除非许可证可用,否则禁用当前线程以进行线程调度。 |
static void | park(Object blocker) | 除非许可证可用,否则禁用当前线程以进行线程调度。 |
static void | parkNanos(long nanos) | 除非许可证可用,否则禁用当前线程以进行线程调度,直到指定的等待时间。 |
static void | parkNanos(Object blocker, long nanos) | 除非许可证可用,否则禁用当前线程以进行线程调度,直到指定的等待时间。 |
static void | parkUntil(long deadline) | 除非许可证可用,否则禁用当前线程以进行线程调度,直到指定的截止时间。 |
static void | parkUntil(Object blocker, long deadline) | 除非许可证可用,否则禁用当前线程以进行线程调度,直到指定的截止时间。 |
static void | unpark(Thread thread) | 如果给定线程尚不可用,则为其提供许可。 |
3、线程等待唤醒机制
3.1、3种让线程等待和唤醒的方法
使用Object中的
wait()
方法让线程等待,使用Object中的notify()
方法唤醒线程使用JUC包中
Condition
的await()
方法让线程等待,使用signal()
方法唤醒线程LockSupport
类可以阻塞当前线程以及唤醒指定被阻塞的线程image-20220710125848925
3.2、Object类中的wait和notify方法实现线程等待和唤醒
正常案例
@Test public void demo1() { Object objectLock = new Object(); new Thread(() -> { synchronized (objectLock) { System.out.println(Thread.currentThread().getName() + "\t ---- come in"); try { objectLock.wait();//----------------------这里先让他等待 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "\t" + "---被唤醒了"); }, "t1").start(); //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { synchronized (objectLock) { objectLock.notify();//-------------------------再唤醒它 System.out.println(Thread.currentThread().getName() + "\t ---发出通知"); } }, "t2").start(); }
打印结果
image-20220710225729930
异常1—去掉synchronized
- 说明要使用wait和notify必须加synchronized
@Test public void demo2() { Object objectLock = new Object(); new Thread(() -> { // synchronized (objectLock) { System.out.println(Thread.currentThread().getName() + "\t ---- come in"); try { objectLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } // } System.out.println(Thread.currentThread().getName() + "\t" + "---被唤醒了"); }, "t1").start(); //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { // synchronized (objectLock) { objectLock.notify(); System.out.println(Thread.currentThread().getName() + "\t ---发出通知"); // } }, "t2").start(); }
打印结果
image-20220710230759240
异常2—把notify和wait的执行顺序对换
- 说明顺序不能对换
public static void main(String[] args) { Object objectLock = new Object(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (objectLock) { System.out.println(Thread.currentThread().getName() + "\t ---- come in"); try { objectLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "\t" + "---被唤醒了"); }, "t1").start(); new Thread(() -> { synchronized (objectLock) { objectLock.notify();//这个先执行了 System.out.println(Thread.currentThread().getName() + "\t ---发出通知"); } }, "t2").start(); }
打印结果
image-20220710230230791
小总结
- wait和notify方法必须要在同步块或者方法里面,且成对出现使用
- 先wait后notify才OK,顺序
3.3、Condition接口中的await后signal方法实现线程的等待和唤醒
正常
@Test public void demo1() { Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); new Thread(() -> { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\t-----come in"); condition.await(); System.out.println(Thread.currentThread().getName() + "\t -----被唤醒"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }, "t1").start(); //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { lock.lock(); try { condition.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } System.out.println(Thread.currentThread().getName() + "\t" + "我要进行唤醒"); }, "t2").start(); }
打印结果
image-20220710231015879
异常1-不加锁
@Test public void demo2() { Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); new Thread(() -> { // lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\t-----come in"); condition.await(); System.out.println(Thread.currentThread().getName() + "\t -----被唤醒"); } catch (InterruptedException e) { e.printStackTrace(); } finally { // lock.unlock(); } }, "t1").start(); //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { // lock.lock(); try { condition.signal(); } catch (Exception e) { e.printStackTrace(); } finally { // lock.unlock(); } System.out.println(Thread.currentThread().getName() + "\t" + "我要进行唤醒"); }, "t2").start(); }
- 打印结果:
IllegalMonitorStateException
- 打印结果:

异常2-
condition.await()
;和condition.signal()
顺序互换public static void main(String[] args) { Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); new Thread(() -> { //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\t-----come in"); condition.await(); System.out.println(Thread.currentThread().getName() + "\t -----被唤醒"); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } }, "t1").start(); new Thread(() -> { lock.lock(); try { condition.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } System.out.println(Thread.currentThread().getName() + "\t" + "我要进行唤醒"); }, "t2").start(); }
打印结果:
image-20220710232211134
小总结
await
和signal
类似于上面wait
和notify
- Condition中的线程等待和唤醒方法,需要先获取锁
- 一定要先await后signal,不能反了
3.4、Object和Condition使用的限制条件
- 总结
- 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
- 必须要先等待后唤醒,线程才能够被唤醒
3.5、LockSupport类中的park等待和unpark唤醒
通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
官网解释:https://www.runoob.com/manual/jdk11api/java.base/java/util/concurrent/locks/LockSupport.html
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport类使用了一种名为Permit(许可) 的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),
permit(许可)只有两个值1和0,默认是0。0 是阻塞,1是唤醒
可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
许可(Permit)
3.5.1、阻塞
park()/park(Object blocker)
调用
LockSupport.park()
时,发现它调用了unsafe类
,并且默认传了一个0public static void park() { UNSAFE.park(false, 0L); }
permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,然后会将permit再次设置为零并返回。
3.5.2、唤醒
调用
LockSupport.unpark();
时,也调用了unsafe类
public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); }
调用
unpark
(thread)方法后,就会将thread线程的许可permit
设置成1(注意多次调用unpark
方法,不会累加,permit
值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()
方法会立即返回。
3.5.3、代码
正常+无锁块要求
@Test public void demo1() { Thread t1 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "\t----------come in"); LockSupport.park(); System.out.println(Thread.currentThread().getName() + "\t----------被唤醒了"); }, "t1"); t1.start(); new Thread(() -> { LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName() + "\t-----发出通知,去唤醒t1"); }, "t2").start(); }
打印结果
image-20220710233052691
之前错误的先唤醒后等待,LockSupport照样支持
@Test public void demo2() { Thread t1 = new Thread(() -> { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t----------come in" + "\t" + System.currentTimeMillis()); LockSupport.park(); System.out.println(Thread.currentThread().getName() + "\t----------被唤醒了" + "\t" + System.currentTimeMillis()); }, "t1"); t1.start(); new Thread(() -> { LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName() + "\t-----发出通知,去唤醒t1"); }, "t2").start(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { throw new RuntimeException(e); } }
打印结果
image-20220710233337915
- sleep方法3秒后醒来,执行park无效,没有阻塞效果,解释如下。先执行了unpark(t1)导致上面的park方法形同虚设无效,时间是一样的 - 类似于高速公路的ETC,提前买好了通行证unpark,到闸机处直接抬起栏杆放行了,没有park拦截了。
- 成双成对要牢记
许可证是只要一个的
public static void main(String[] args) { Thread t1 = new Thread(() -> { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t----------come in" + "\t" + System.currentTimeMillis()); LockSupport.park(); LockSupport.park();//第二个阻塞 System.out.println(Thread.currentThread().getName() + "\t----------被唤醒了" + "\t" + System.currentTimeMillis()); }, "t1"); t1.start(); new Thread(() -> { LockSupport.unpark(t1); LockSupport.unpark(t1); LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName() + "\t-----发出通知,去唤醒t1"); }, "t2").start(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { throw new RuntimeException(e); } }
打印结果
image-20220710233930949
小总结
Lock Support是用来创建锁和其他同步类的基本线程阻塞原语。
Lock Support是一个线程阻塞工具类, 所有的方法都是静态方法, 可以让线程在任意位置阻塞, 阻塞之后也有对应的唤醒方法。归根结底, Lock Support调用的Unsafe中的native代码。
Lock Support提供park() 和unpark() 方法实现阻塞线程和解除线程阻塞的过程
Lock Support和每个使用它的线程都有一个许可(permit) 关联。
每个线程都有一个相关的permit, permit最多只有一个, 重复调用un park也不会积累凭证。
形象的理解
- 线程阻塞需要消耗凭证(permit) , 这个凭证最多只有1个。
- 当调用方法时
- 如果有凭证,则会直接消耗掉这个凭证然后正常退出;
- 如果无凭证,就必须阻塞等待凭证可用;
- 而则相反, 它会增加一个凭证, 但凭证最多只能有1个, 累加无效。
面试题
为什么可以突破wait/notify的原有调用顺序?
- 因为un park获得了一个凭证, 之后再调用park方法, 就可以名正言顺的凭证消费, 故不会阻塞。先发放了凭证后续可以畅通无阻。
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
- 因为凭证的数量最多为1, 连续调用两次un park和调用一次un park效果一样, 只会增加一个凭证;而调用两次park却需要消费两个凭证, 证不够, 不能放行。