线程间的协作——等待唤醒机制

等待/通知机制

等待唤醒机制就是多个线程间的一种协作机制,谈到线程我们经常想到的是线程间的竞争,比如各个线程争夺锁,但是多个线程也会有协作的机制,就好比在公司中你与你的同事是竞争关系,但是你们也会合作完成某个项目

等待唤醒机制就是在一个线程进行了规定操作后,另一个线程就进入等待状态(wait()),等待刚刚进行规定操作的线程执行完他们的指定代码后,再将处于等待状态的这个线程唤醒(notify());在有多个线程进行等待的时候,如果需要我们可以使用notifyAll()来唤醒所有的等待线程。

wait/notify就是线程间的一种协作机制

等待通知机制的方法

notify():

通知一个在对象上等待的线程,使其从wait方法唤醒。而唤醒的前提是该线程获取到了对象的锁。没有获取到的线程依旧处以WAITING状态。

notifyAll():

通知所有等待在该对象上的线程。

wait():

调用该方法的线程进入WAITING状态,只有等待另外线程的通知或被interrupted中断才会被唤醒。需要注意的是,调用wait()方法后,会释放对象的锁。

等待超时模式

等待超时模式就是在等待/通知机制上增加了超时控制,并保留了通知机制,即使方法执行时间过长,也不会永久阻塞调用者,而是会按照调用者的要求按时返回。若是其他线程提前对等待线程进行了唤醒,则可以提前结束等待。

我们发现,等待超时不仅仅是线程与线程之间的协作,现在可以称之为线程与线程以及时间的协作。

等待超时模式的方法

因为等待超时模式虽然是由时间控制唤醒的,因此参数中加入了时间。我们的notify系列的方法同样可以对超时等待的线程生效。

wait(long)

超过参数long(毫秒)后,如果未被其他地方唤醒,则自动唤醒。

wait(long,int)

对于超时时间更细粒度的控制,可以达到纳秒。

等待/通知模式范式

等待遵循如下原则

1. 获取对象的锁。

2. 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。

3. 条件不满足则执行对应的逻辑。

图片1.png

通知遵循如下原则

1. 获取对象的锁

2. 改变条件,让等待方法中while()中的wait被唤醒后可以通过下一次条件判断

3. 唤醒所有等待中的线程竞争锁,成功竞争到锁的线程判断是否符合条件,符合条件的继续执行,不符合的继续等待。

图片2.png

在调用wait,notify系列方法之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait,notify方法。

等待唤醒机制的执行流程

  1. 执行wait方法后,当前线程阻塞在此处,并将锁释放。此时其他竞争锁的线程都会再次竞争(下次执行等待或是唤醒的线程完全随机)。
  2. 执行notify系列方法的后,某个或多个wait中的线程此时会被唤醒,重新加入竞争锁的行列中。当竞争成功后,会继续从之前wait阻塞的位置继续执行。
  3. 执行notify系列方法的线程,直到synchronized代码块执行完毕后释放锁,并重新竞争锁。

notify和notifyAll的区别与使用场景

notify无法确定被唤醒的是哪个线程。因此在一对一等待唤醒中可以使用。但在使用此锁的wait方法超过2个时,应使用notifyAll。

等待超时模式实现一个连接池

需求与设计思路

调用一个方法时,等待一段时间(一般来说是给定一个时间段)。如果该方法能够在给定的时间段之内得到结果,那么将结果立刻返回。否则超时返回默认结果。

假设等待时间段是T,那么可以推断出当前时间为now+T之后就会超时。

等待持续时间 第一次为T,后续为(起始now+T)-当前now

超时时间为 Future=now+T

程序实现步骤

首先我们把连接池中的线程在此处模拟成一个List集合,因此设置成员变量pool,List的长度代表连接池的长度。

image.png

接着,我们设置一个构造函数, 来初始化线程池的初始大小(实际上就是设置List长度)。

image.png

我们知道,线程池中的线程有获取与归还的概念,当调用层获取到一个线程池中的线程,也就代表我们的线程池中的线程会减少一个。当线程池中的线程全部分派出去后,调用层继续获取线程池中的线程时,就需要等待其他正在使用线程池中的线程的任务执行完毕并归还时,才能获取成功。

那么此时获取线程池中的线程就有两种方案:

  1. 死等,直到别的持有线程池线程的任务执行完毕并归还后,再成功获取线程。
  2. 设置一个等待时长,若是在时长内无法成功获取线程,则向调用层返回null(获取失败)。

我们来看看具体的实现方案:

image.png

那么归还连接的逻辑是怎样的呢?

image.png

这样,我们一个基于等待唤醒机制的线程池就实现了。当然这个线程池不是好,最大的缺点就是有太多轮循判断,很容易造成CPU占用率超高。

实际中,我们的线程池底层是使用阻塞队列实现的。

yield(),sleep(),wait(),notify()等方法对锁有何影响?

 

调用yield.sleep后,都不会释放当前线程锁持有的锁。

调用wait方法后,会释放当前线程持有的锁,并阻塞在wait方法处。而且当前被唤醒后,会重新去竞争锁。当该线程再次竞争到锁后,继续执行wait方法阻塞位置后面的代码。

调用notify系列方法后,对锁无影响,线程只有在synchronized同步代码执行完后才会自然而然的释放锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大将黄猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值