欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 时评 > Java中常见的等待唤醒机制及实践

Java中常见的等待唤醒机制及实践

2024/10/26 0:26:47 来源:https://blog.csdn.net/weixin_43975276/article/details/142754025  浏览:    关键词:Java中常见的等待唤醒机制及实践

JDK自带的等待唤醒机制

在Java中,有一个JDK维度的等待唤醒机制。Object类的wait和notify,notifyAll
需要在synchronized同步代码块内并且对象必须获取到锁才能调用。否则会抛IllegalMonitorStateException异常。
当线程在尝试获取锁时失败,会被封装成节点(Node)并加入到等待队列中,进行同步等待。
等待队列底层是一条维护着头尾指针的Node节点链表。

					Object obj=new Object();synchronized (obj){System.out.println(Thread.currentThread().getId()+"开始");obj.wait(3000);System.out.println(Thread.currentThread().getId()+"结束");}

如上,obj是synchronized代码块的锁对象,在同步代码块内的线程必定获取到了obj对象的对象锁,因而可以调用obj的等待唤醒方法。
调用obj.wati(),当前线程会释放obj对象的锁,并进入obj对象的等待队列,等待被其他线程调用obj对象的notify或notifyAll唤醒。

public class NotifyTest {public static void main(String[] args) {Object obj=new Object();new Thread(new Runnable() {@Overridepublic void run() {try {synchronized (obj){System.out.println(Thread.currentThread().getId()+"开始"+ new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));obj.wait(5000);System.out.println(Thread.currentThread().getId()+"结束"+new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));}} catch (InterruptedException e) {e.printStackTrace();}}}).start();new Thread(new Runnable() {@Overridepublic void run() {try {synchronized (obj) {System.out.println(Thread.currentThread().getId() + "开始了"+new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));Thread.sleep(1000);obj.notifyAll();Thread.sleep(1000);System.out.println(Thread.currentThread().getId() + "结束了"+new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));}} catch (Exception e) {e.printStackTrace();}}}).start();}
}

在这里插入图片描述

如上图,线程12先获取锁,并执行打印,然后调用wait方法释放obj的锁并进入obj等待队列,线程13也启动并执行打印,然后调用notifyAll方法唤醒线程12,但是调用notifyAll方法只是会唤醒线程12,并不会立刻释放锁(线程13唤醒线程12之后睡了1秒),所以是先执行完线程13的打印,释放obj锁,然后线程12再获取锁并打印后续。

Lock的等待唤醒机制

条件队列的前置条件是获取到Lock接口和Condition接口
需要创建Lock对象,然后调用newCondition方法创建Condition对象。这个Condition对象就是一个条件队列,底层是一条维护着头尾指针的链表。条件队列的具体条件逻辑需要用业务代码去实现。
一个Lock对象可以创建多个Condition对象,也就是说一个Lock对象可以有多个条件队列,并且不同条件队列之间互不影响。因而可以实现更细维度的线程睡眠唤醒机制。
如ReentrantLock类的newCondition方法每次都会返回一个新的Condition对象,可以用以区分不同的等待条件。

public class NotifyTest {public static void main(String[] args) {Lock lock = new ReentrantLock();Condition condition1 = lock.newCondition();Condition condition2 = lock.newCondition();new Thread(new Runnable() {@Overridepublic void run() {try {System.out.println(Thread.currentThread().getId() + "条件1开始" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));lock.lock();System.out.println(Thread.currentThread().getId() + "条件1获取到锁" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));condition1.await();} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println(Thread.currentThread().getId() + "条件1结束" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));lock.unlock();}}}).start();new Thread(new Runnable() {@Overridepublic void run() {try {System.out.println(Thread.currentThread().getId() + "条件2开始" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));lock.lock();System.out.println(Thread.currentThread().getId() + "条件2获取到锁" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));condition2.await();} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println(Thread.currentThread().getId() + "条件2结束" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));lock.unlock();}}}).start();new Thread(new Runnable() {@Overridepublic void run() {try {System.out.println(Thread.currentThread().getId() + "开始" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));//先睡一秒,确保上面启动的两个线程先后获取锁并阻塞Thread.sleep(1000);lock.lock();System.out.println(Thread.currentThread().getId() + "获取到锁" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));condition2.signal();Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();} finally {System.out.println(Thread.currentThread().getId() + "结束" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));lock.unlock();}}}).start();}
}

在这里插入图片描述

如上图,三个线程先后启动,唤醒的线程先睡眠一秒,防止和条件1条件2线程抢锁。条件1和条件2按照先后顺序拿到锁,分别调用不同的条件对象阻塞等待条件。接着唤醒线程睡眠一秒之后唤醒,并获取锁,打印,然后唤醒等待条件2的线程,执行完毕释放锁。锁释放之后,条件2的线程检测到锁可用,就获取锁,并继续执行。而条件1的线程因为没有被唤醒,一直在阻塞等待唤醒条件。

可以通过业务代码去限制不同的线程所需的唤醒条件,更精细维度的锁控制。

结论

  • JDK自带的等待唤醒机制实现简单,所有等待的线程都阻塞在锁对象上,所有等待的线程都参与获取锁。

  • Lock实现的等待唤醒机制可以实现条件等待,也就是等待获取锁的线程,需要先达到某种条件被唤醒(从条件队列移到等待队列)才能参与获取锁,这样可以实现更加精细化的线程控制。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com