基本使用
// 暂停当前线程LockSupport.park(); // 恢复某个线程的运行LockSupport.unpark(暂停线程对象)
特点
与 Object 的 wait & notify 相比
- wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park,unpark 不必
- park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,就不那么【精确】
- park & unpark 可以先 unpark,而 wait & notify 不能先 notify
先Park后Unpark
packagecom.gzczy.concurrent.week3; importlombok.extern.slf4j.Slf4j; importjava.util.concurrent.TimeUnit; importjava.util.concurrent.locks.LockSupport; /*** @Description Park和Unpark* @Author chenzhengyu* @Date 2020-11-05 19:15*/topic="c.ParkAndUnParkDemo") (publicclassParkAndUnParkDemo { publicstaticvoidmain(String[] args) { // demo1();demo2(); } publicstaticvoiddemo1(){ Threadt1=newThread(() -> { log.debug("start..."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedExceptione) { e.printStackTrace(); } log.debug("park..."); LockSupport.park(); log.debug("resume..."); },"t1"); t1.start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedExceptione) { e.printStackTrace(); } log.debug("unpark..."); LockSupport.unpark(t1); } //先Unpark再Parkpublicstaticvoiddemo2(){ Threadt1=newThread(() -> { log.debug("start..."); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedExceptione) { e.printStackTrace(); } log.debug("park..."); LockSupport.park(); log.debug("resume..."); }, "t1"); t1.start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedExceptione) { e.printStackTrace(); } log.debug("unpark..."); LockSupport.unpark(t1); } }
源码解析
为线程调度目的禁用当前线程,除非许可可用。
如果许可证是可用的,那么它将被消耗,调用将立即返回。否则,当前线程会因为线程调度的目的而被暂停,并处于休眠状态,直到以下三种情况之一发生:
- 其他一些线程以当前线程为目标调用unpark
- 其他线程打断当前线程
- 不符合逻辑的调用
park方法不会告知你是哪一种情况的发生而导致的。调用者应该重新检查导致线程停在第一个位置的条件。调用者也可以确定,例如,线程返回时的中断状态。
/*** Disables the current thread for thread scheduling purposes unless the* permit is available.** <p>If the permit is available then it is consumed and the call returns* immediately; otherwise* the current thread becomes disabled for thread scheduling* purposes and lies dormant until one of three things happens:** <ul>* <li>Some other thread invokes {@link #unpark unpark} with the* current thread as the target; or** <li>Some other thread {@linkplain Thread#interrupt interrupts}* the current thread; or** <li>The call spuriously (that is, for no reason) returns.* </ul>** <p>This method does <em>not</em> report which of these caused the* method to return. Callers should re-check the conditions which caused* the thread to park in the first place. Callers may also determine,* for example, the interrupt status of the thread upon return.** @param blocker the synchronization object responsible for this* thread parking* @since 1.6*/publicstaticvoidpark(Objectblocker) { Threadt=Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null); }
unpark通过传入当前线程进行取消park
/*** Makes available the permit for the given thread, if it* was not already available. If the thread was blocked on* {@code park} then it will unblock. Otherwise, its next call* to {@code park} is guaranteed not to block. This operation* is not guaranteed to have any effect at all if the given* thread has not been started.** @param thread the thread to unpark, or {@code null}, in which case* this operation has no effect*/publicstaticvoidunpark(Threadthread) { if (thread!=null) UNSAFE.unpark(thread); }
对比Wait & Notify 优势
LockSupport的主要作用就是让线程进入阻塞等待和唤醒状态
我们常见的三种线程唤醒的方法
- 方式1: 使用Object中的wait()方法让线程等待, 使用Object中的notify()方法唤醒线程
- 方式2: 使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
- 方式3: LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
Object类中的wait和notify方法实现线程等待和唤醒
packagecom.gzczy.concurrent.heima.b.wait; importlombok.extern.slf4j.Slf4j; importjava.util.concurrent.TimeUnit; /*** @Description wait notify 测试阻塞* @Author chenzhengyu* @Date 2021年01月31日 14:12:08*/topic="c.WaitNotifyDemo") (publicclassWaitNotifyDemo2 { finalstaticObjectobj=newObject(); publicstaticvoidmain(String[] args) throwsException { newThread(() -> { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedExceptione) { e.printStackTrace(); } synchronized (obj) { log.debug("执行...."); try { obj.wait(); // 让线程在obj上一直等待下去 } catch (InterruptedExceptione) { e.printStackTrace(); } log.debug("其它代码...."); } },"t1").start(); newThread(() -> { synchronized (obj) { log.debug("执行...."); obj.notify(); // 让线程在obj上一直等待下去log.debug("其它代码...."); } },"t2").start(); } }
异常1:wait方法和notify方法,两个都去掉同步代码块,抛出IllegalMonitorStateException
ibary/Java/JavavintuaLyachines/jk1.8..26.k/m/in/ava
c.waitNotifyDemo-执行....
14:13:08.483[t2]
javaLangLALMontxceptioncerakoint
Exceptioninthread"t2"java.1
atjava.tang.@bjectnotifyNatieM)
internalcaii>
t8om.2c2y.m.
14:13:10.484[t1]Wii.
javaagotxceerakint
ninthread"t1"j
Exception
山兰
atjava.Lang.objectwaitNativeMethod
atjava.tang.object.waitcobicta2
tcon.gzczyor.he
internaZcali>
Processfinishedwithexitcode
异常2:将notify放在wait方法前面,程序将无法执行,无法进行唤醒
WaitNotifyDemo2
Run:
Library/Java/bavavirtuaLMachin/jk1.86on
14:12:34.336[t2]cWaitoifeo行...
cWaitotifyDemo其它代码....
14:12:34.339[t2]
$
14:12:36.334[t1]
CWaitnotifyDemo-执行....
力Q引巾
总结:wait和notify方法必须要在同步代码块中或者方法里面成对进行出现才能使用,而且必须先wait后notify之后才OK
通过Park和Unpark进行实现线程等待和唤醒
packagecom.gzczy.concurrent.heima.b.wait; importlombok.extern.slf4j.Slf4j; importjava.util.concurrent.TimeUnit; importjava.util.concurrent.locks.LockSupport; /*** @Description LockSupport Demo 进行线程阻塞* @Author chenzhengyu* @Date 2021-01-31 14:22*/topic="c.LockSupportDemo") (publicclassLockSupportDemo { publicstaticvoidmain(String[] args) throwsInterruptedException { //默认是permit 0Threadt1=newThread(() -> { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedExceptione) { e.printStackTrace(); } log.debug("执行....是否打断--->"+Thread.currentThread().isInterrupted()); // 调用一次park就会消费permit 由刚刚的1变为0 你继续运行下去吧LockSupport.park(); log.debug("其它代码,是否打断--->"+Thread.currentThread().isInterrupted()); //再次打住 目前没有许可了 许可为0 那就不放你继续运行啦LockSupport.park(); log.debug("再次park,是否打断--->"+Thread.currentThread().isInterrupted()); //被打断后会发现无法park住,打断标记已经为true 线程已经被打断了LockSupport.park(); }, "t1"); t1.start(); newThread(() -> { log.debug("执行...."); //调用一次unpark就加1变成1,线程还在运行 没毛病老铁 你继续运行吧LockSupport.unpark(t1); log.debug("其它代码...."); }, "t2").start(); try { TimeUnit.SECONDS.sleep(4); log.debug("打断t1线程...."); t1.interrupt(); } catch (InterruptedExceptione) { e.printStackTrace(); } } }
运行结果
LockSupportDemo
Run:
Library/Java/JavavirtuaLMachins/dk1.8..26.k//
15:03:12.156[t2]c.Lueo行..
15:03:12.159[t2]c.Lockuppoemo它....
15:83141551].m打
口粽兄
个少
15:8314.156[1.m,否打断-->lse
15:03:16.158[main].u打断程
15:0316.1打
finishedwithexitcode
Process1
优势对比
- 以前的等待唤醒通知机制必须synchronized里面有一个wait和notify;Lock里面有await和signal。LockSupport不用持有锁块,不用加锁,程序性能好
- 先后顺序,不容易导致卡死,虚假唤醒。通过许可证进行授权
解析
每个线程都有自己的一个 Parker 对象(底层C代码实现),由三部分组成 _counter , _cond 和 _mutex 打个比喻
- 线程就像一个旅人,Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter 就好比背包中的备用干粮(0 为耗尽,1 为充足)
- 调用 park 就是要看需不需要停下来歇息
- 如果备用干粮耗尽,那么钻进帐篷歇息
- 如果备用干粮充足,那么不需停留,继续前进
- 调用 unpark,就好比令干粮充足
- 如果这时线程还在帐篷,就唤醒让他继续前进
- 如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进
- 因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮
当前线程调用UnSafe.park
Unsafepark
(1)
Parker-0
Thread-o
Thread-o
mutex
cond
(3)
(4).counter-0
counter三0
(2)
- 检查 _counter ,本情况为 0,这时,获得 _mutex 互斥锁
- 线程进入 _cond 条件变量阻塞
- 设置 _counter = 0
当前线程调用UnSafe.unpark(许可为0)
Unsafe.unpark(Thread0)
Parker-0
Thread-0
Thread-o
mutex
cond
(3)
counter1
黑马程序员
witheima.com
(4)_counter-0
- 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
- 唤醒 _cond 条件变量中的 Thread_0
- Thread_0 恢复运行
- 设置 _counter 为 0
当前线程调用UnSafe.unpark(许可为1)
Unsafe.unpark(Threado
Unsafe.park
(2)
Parker-0
Thread-o
mutex
cond
(4).counter-0
counter-1
(3)
1. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
2. 当前线程调用 Unsafe.park() 方法
3. 检查 _counter ,本情况为 1,这时线程无需阻塞,继续运行
4. 设置 _counter 为 0
总结
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,它使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(Permit),Permit只有两个值:1和0,默认是0。我们可以把许可看成是一种信号量(Semaphore),但是与Semaphore不同的是,许可的累加上限是1
LockSupport是一个线程阻塞工具,所有的方法都是静态方法,可以让线程在任意位置进行阻塞。阻塞之后也有对应的唤醒方法,归根结底,LockSupport调用的是UnSafe里面的native代码
LockSupport提供了Park和Unpark的方法实现阻塞线程和解除线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可(Permit)进行关联,Permit相当于1,0的开关,默认是0
调用一次unpark就加1变成1,调用一次park就会消费permit,也就是将1变成0的这个过程,同时将park立即返回
如果再次调用park会变成阻塞(因为Permit为0,所以会阻塞在这里,直至Permit变为1),如果这个时候调用unpark会把permit设置为1。每个线程都有一个相关的Permit,Permit最多只有1个,重复调用unpark也不会积累凭证
面试题
为什么可以先唤醒线程后阻塞线程?
答:因为UnPark获得了一个凭证,之后调用Park方法,就可以名正言顺的凭证消费,故不会阻塞
为什么唤醒两次后阻塞两次,但是最终的结果还是会阻塞线程?
答:因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark的效果是一样的,只会增加一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行