建设部网站查询造价师证件,柳州网站建设33,镇江房地产网站建设,昆山市住房和建设局网站1、为什么需要线程的等待和唤醒
线程的等待唤醒机制是一种经典的“生产者和消费者”模型。例如食品加工厂#xff0c;食品加工人员和原料补给人员#xff0c;在有充足原料时#xff0c;补给人员是在等待#xff0c;等到原料不够时#xff0c;食品加工人员通知补给人员食品加工人员和原料补给人员在有充足原料时补给人员是在等待等到原料不够时食品加工人员通知补给人员唤醒。在我们开发诸如此类需求的时候两个线程之间协调等待和唤醒还是很有必要的。那咱们看看都有那些方式实现线程等待和唤醒机制。
2、线程等待和唤醒机制方式
实现线程等待和唤醒机制主要有3中方式分别是
使用 Object 中的 wait() 方法让线程等待使用 Object 中的 notify() 方法唤醒线程使用 JUC 包中 Condition 的 await() 方法让线程等待使用 signal() 方法唤醒线程使用LockSupport类的park()方法让线程等待使用unpark()方法唤醒线程。 那这三种方式具体怎么使用都有那些优缺点呢我们通过实例代码演示。
3、线程等待和唤醒机制实例
3.1、使用 Object 中的 wait() 方法让线程等待使用 Object 中的 notify() 方法唤醒线程
代码
package com.lc.test02;import java.util.concurrent.TimeUnit;/*** author liuchao* date 2023/4/8*/
public class ThreadWaitOne {/*** 注意必须使用同一把锁*/static Object lock new Object();public static void main(String[] args) {/*** 线程1*/Thread t1 new Thread(() - {synchronized (lock) {System.out.println(进入 Thread.currentThread().getName());try {lock.wait();} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println(Thread.currentThread().getName() 被唤醒);}}, t1);t1.start();try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {Thread.currentThread().interrupt();}/*** 线程2*/Thread t2 new Thread(() - {synchronized (lock) {System.out.println(进入 Thread.currentThread().getName());lock.notify();System.out.println(唤醒通知已发);}}, t2);t2.start();}
}效果 进入t1 进入t2 唤醒通知已发 t1被唤醒 总结此种方式必须使用同一把锁并且必须包含在synchronized代码块中如果未使用synchronized包裹则会报错。 Exception in thread t1 java.lang.IllegalMonitorStateException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:502) at com.lc.test02.ThreadWaitOne.lambda$main$0(ThreadWaitOne.java:24) at java.lang.Thread.run(Thread.java:750) 并且wait方法的调用必须要在notify/notifyAll的调用之前否则线程将永远不会被唤醒。
3.2、使用 JUC 包中 Condition 的 await() 方法让线程等待使用 signal() 方法唤醒线程
代码
package com.lc.test02;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** author liuchao* date 2023/4/8*/
public class ThreadWaitTwo {/*** 必须是同一把锁*/static Lock lock new ReentrantLock();static Condition condition lock.newCondition();public static void main(String[] args) {/*** 线程1*/Thread t1 new Thread(() - {lock.lock();try {System.out.println(进入 Thread.currentThread().getName());condition.await();} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {lock.unlock();}System.out.println(Thread.currentThread().getName() 被唤醒);}, t1);t1.start();try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {Thread.currentThread().interrupt();}/*** 线程2*/Thread t2 new Thread(() - {lock.lock();try {System.out.println(进入 Thread.currentThread().getName());condition.signal();System.out.println(唤醒通知已发);} finally {lock.unlock();}}, t2);t2.start();}
}效果 进入t1 进入t2 唤醒通知已发 t1被唤醒 总结使用此中方式必须配合lock代码必须被lock包裹否则将报错 Exception in thread t1 java.lang.IllegalMonitorStateException 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.lc.test02.ThreadWaitTwo.lambda$main$0(ThreadWaitTwo.java:28) at java.lang.Thread.run(Thread.java:750) 并且await方法的调用必须在signal/signalAll方法调用之前否则t1也是不会被唤醒
3.3、使用LockSupport类的park()方法让线程等待使用unpark()方法唤醒线程。
LockSupport 类使用了一种名为 Permit 许可的概念来做到阻塞和唤醒线程的功能每个线程都有一个许可permitpermit 只有两个值 1 和 0默认是 0
阻塞①、park()/park(Object blocker) ②、permit 默认是 0所以一开始调用 park() 方法当前线程就会阻塞直到别的线程将当前线程的 permit 设置为 1 时park 方法会被唤醒然后会将 permit 再次设置为 0 并返回。 ③、阻塞当前线程/阻塞传入的具体线程
唤醒①、unpark(Thread thread) ②、调用 unpark(Thread thread) 方法后就会将 thread 线程的许可 permit 设置为 1注意多次调用 unpark 方法不会累加permit 值还是 1会自动唤醒 thread 线程即之前阻塞中的 LockSupport.park()方法会立即返回 ③、唤醒处于阻塞状态的指定线程
代码
package com.lc.test02;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;/*** author liuchao* date 2023/4/8*/
public class ThreadWaitThree {public static void main(String[] args) {/*** 线程1*/Thread t1 new Thread(() - {System.out.println(进入 Thread.currentThread().getName());LockSupport.park();System.out.println(Thread.currentThread().getName() 被唤醒);}, t1);t1.start();try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {Thread.currentThread().interrupt();}/*** 线程2*/Thread t2 new Thread(() - {System.out.println(进入 Thread.currentThread().getName());LockSupport.unpark(t1);System.out.println(唤醒通知已发);}, t2);t2.start();}
}效果 进入t1 进入t2 唤醒通知已发 t1被唤醒 结论此种方式不需要增加同步机制天生就是无锁机制实现线程等待和唤醒并且因为是通过许可方式来唤醒线程的所以许可是在等待(调用park()方法)前还是后是不影响的park和unpark调用先后顺序我关此种方式也是我们推荐使用的方式。当然这种方式也是有缺点的许可最多只有一个如果等待多次调用多次park()方法,线程也就永远不能唤醒了。
演示线程的唤醒和unpark先调用还是后调用无关
package com.lc.test02;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;/*** author liuchao* date 2023/4/8*/
public class ThreadWaitThree {public static void main(String[] args) {/*** 线程1*/Thread t1 new Thread(() - {try {//等待300毫秒让t2先执行TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println(进入 Thread.currentThread().getName());LockSupport.park();System.out.println(Thread.currentThread().getName() 被唤醒);}, t1);t1.start();/*** 线程2*/Thread t2 new Thread(() - {System.out.println(进入 Thread.currentThread().getName());LockSupport.unpark(t1);System.out.println(唤醒通知已发);}, t2);t2.start();}
}效果 进入t2 唤醒通知已发 进入t1 t1被唤醒 演示多次调用unpark多次调用park导致线程不能被唤醒
package com.lc.test02;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;/*** author liuchao* date 2023/4/8*/
public class ThreadWaitThree {public static void main(String[] args) {/*** 线程1*/Thread t1 new Thread(() - {System.out.println(进入 Thread.currentThread().getName());/*** 调用两次park方法*/LockSupport.park();LockSupport.park();System.out.println(Thread.currentThread().getName() 被唤醒);}, t1);t1.start();try {//等待300毫秒让t2先执行TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {Thread.currentThread().interrupt();}/*** 线程2*/Thread t2 new Thread(() - {System.out.println(进入 Thread.currentThread().getName());/*** 调用了两次park我们调用三次unpark 还是无法唤醒t1线程的*/LockSupport.unpark(t1);LockSupport.unpark(t1);LockSupport.unpark(t1);System.out.println(唤醒通知已发);}, t2);t2.start();}
}