您的位置 首页 java

“全栈2019”Java多线程第三十二章:显式锁Lock等待唤醒机制详解

难度

初级

学习时间

30分钟

适合人群

零基础

开发语言

java

开发环境

  • JDK v11
  • IntelliJ IDEA v2018.3

友情提示

  • 本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。
  • 本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!

1.温故知新

前面在 一章中介绍了 lock 尝试获取锁tryLock()方法

在 一章中介绍了 显式锁Lock中的lockInterruptibly()方法:在如果当前线程没被中断,则一直获取锁。

现在我们来讲解 显式锁Lock的等待唤醒机制。

2.隐式锁的等待唤醒机制

前面在 一章中介绍了 等待唤醒机制 。只不过是隐式锁的,下面我们简单回顾一下隐式锁的等待唤醒机制。

等待唤醒机制必备条件:

  • 多个线程
  • 同一把锁
  • 等待:wait()/wait(long timeoutMillis)/wait(long timeoutMillis, int nanos)方法
  • 唤醒:notify()/notifyAll()方法

多个线程

单线程无法做到等待唤醒,这里我们创建两个线程:

线程创建之后,重写run()方法:

好了,多个线程的条件已经具备。

同一把锁

先把锁创建出来:

然后,在两个线程的run()方法中写上同步代码块:

同步对象为刚刚创建出来的锁:

好了,同一把锁的条件完成。

等待:wait()/wait(long timeoutMillis)/wait(long timeoutMillis, int nanos)方法

现在只需让其中一个线程等待即可,调用锁的wait()/wait(long timeoutMillis)/wait(long timeoutMillis, int nanos)方法即可。

这里我们就选择调用wait()方法:

wait()方法会抛出异常,大家记得处理一下。

接着,我们在线程等待之前输出一句话,目的只是看下何时被等待:

然后,锁上被等待的线程醒了之后再输出一句话,目的是为了看下线程何时醒的:

好了,等待的条件完成。

唤醒:notify()/notifyAll()方法

接着我们只需要让另外一个线程唤醒锁上被等待的线程即可。

怎么唤醒呢?

调用锁的notify()/notifyAll()方法即可。

既然能执行到锁的notify()/notifyAll()方法,那么必然要得到锁,这是前提。

接着,我们来书写另外一个线程的run()方法,在run()方法中的同步代码块里调用锁的notify()/notifyAll()方法:

好了,唤醒条件已完成。

接下来,我们就可以运行程序看看效果了,不过为了看到更明显的效果,需要在启动线程thread1之后将主线程睡3秒钟,然后再启动线程thread2:

运行程序,执行结果:

从运行结果来看,符合预期。

线程thread1被wait,等待3秒钟之后被唤醒。

3.显式锁的等待唤醒机制

上一小节演示了隐式锁的等待唤醒机制,接下来我们来看看显式锁的等待唤醒机制是怎么样的。

例子还是上一小节的例子,只不过我们要将同步代码块换成显式锁Lock。

先把同步对象移除掉,即类型为Object的lock对象:

移除之后,创建显式锁Lock对象:

接着,将同步代码块替换为显式锁的形式,替换的代码块有以下几处:

将以上标记的几处代码换成显式锁Lock:

替换完成之后,大家发现之前的lock.wait()方法和lock.notifyAll()方法好像还能用:

但,事实真的如此吗?

我们只有运行之后才知道。

运行程序,执行结果:

静图:

文字版:

Thread-0 --- 等待
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
	at java.base/java.lang.Object.wait(Native Method)
	at java.base/java.lang.Object.wait(Object.java:328)
	at main.Main$1.run(Main.java:27)
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
	at java.base/java.lang.Object.notifyAll(Native Method)
	at main.Main$2.run(Main.java:45)
 

IllegalMonitorStateException异常说明什么?

说明线程并没有持有锁。

怎么会呢?我们明明持有锁了:

对的,这里我们确实持有锁了,但是,我们持有的却不是lock对象这把锁:

如果我们这样写那就是持有lock对象这把锁:

现在不是这样写的,我们既然使用了显式锁,那么等待/唤醒也得按照显式锁的方式来。

在显式锁中,等待唤醒机制需要一个 Condition 对象来实现。

单词Condition翻译过来就是 条件 的意思。

如何得到这个Condition对象呢?

调用显式锁的newCondition()方法即可。

4.获取Condition对象newCondition()方法

我们可以调用显式锁的newCondition()方法得到一个新的Condition对象。

newCondition()方法在Lock接口中的源码:

将注释翻译成中文:

中文注释全文:

返回绑定到此Lock实例的新Condition实例。
在等待条件之前,当前线程必须保持锁定。对Condition.await()的调用将在等待之前以原子方式释放锁,并在等待返回之前重新获取锁。
 

去掉注释版:

调用newCondition()方法时,返回绑定到此Lock实例的新Condition实例。

访问权限

Condition :newCondition()方法返回Condition类型值,返回绑定到此Lock实例的新Condition实例。

newCondition()方法只能被对象调用。

参数

无。

抛出的异常

throws UnsupportedOperationException :如果此Lock实现不支持Condition,则抛出此异常。

应用

我们先不要看Condition类里面具体有什么,先把Condition对象获取之后,然后使用Condition对象去调用wait()和notify()/notifyAll()方法看看。

首先,我们先来调用显式锁的newCondition()方法获取到Condition对象:

接着,将lock.wait()和lock.notifyAll()替换为condition.wait()和condition.notifyAll(),下面就是要被替换的代码:

替换之后的代码:

替换完毕之后,我们来运行程序看看效果。

运行程序,执行结果:

静图:

文字版:

Thread-0 --- 等待
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
	at java.base/java.lang.Object.wait(Native Method)
	at java.base/java.lang.Object.wait(Object.java:328)
	at main.Main$1.run(Main.java:30)
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
	at java.base/java.lang.Object.notifyAll(Native Method)
	at main.Main$2.run(Main.java:48)
 

从运行结果来看,好像还是不行。和上一小节的错误一摸一样。

看来还是我们调用的方法有问题。

5.Condition类

实际上,当我们用显式锁Lock替代了synchronized时,Object的wait()、notify()和notifyAll()方法都被Condition对象中的方法所替代。

Object的wait()方法被Condition对象分解为以下几个方法:

  • void await()
  • boolean await​(long time, TimeUnit unit)
  • long awaitNanos​(long nanosTimeout)
  • void awaitUninterruptibly()
  • boolean awaitUntil​(Date deadline)

Object的notify()和notifyAll()方法被Condition对象分解为以下几个方法:

  • void signal()
  • void signalAll()

以上几个方法后面几章都会讲解。

现在,我们就来用Condition的await()方法来替代Object的wait()方法:

用Condition的signalAll()方法来替代Object的notifyAll()方法:

好了,我们再运行程序,看看执行结果:

从运行结果来看,符合预期。线程thread1如期被等待,也如期过了3秒钟之后醒来。

在上面我们也看到了Condition类中有很多方法,下一章我们就来向大家介绍它们怎么使用。

最后,希望大家可以把这个例子照着写一遍,然后再自己默写一遍,方便以后碰到类似的面试题可以轻松应对。

祝大家编码愉快!

GitHub

本章程序GitHub地址:

总结

  • 显式锁Lock需要Condition对象协助完成等待唤醒机制。
  • await()方法造成当前线程在等待,直到它被唤醒,通常由被通知或中断。
  • signalAll()方法作用是唤醒正在此对象监视器上等待的所有线程。

至此,Java中显式锁Lock的等待唤醒机制相关内容讲解先告一段落,更多内容请持续关注。

答疑

如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。

上一章

下一章

“全栈2019”Java多线程第三十三章:await与signal/signalAll

学习小组

加入同步学习小组,共同交流与进步。

  • 方式一:关注头条号Gorhaf,私信“Java学习小组”。
  • 方式二:关注公众号Gorhaf,回复“Java学习小组”。

全栈工程师学习计划

关注我们,加入“全栈工程师学习计划”。

版权声明

原创不易,未经允许不得转载!

文章来源:智云一二三科技

文章标题:“全栈2019”Java多线程第三十二章:显式锁Lock等待唤醒机制详解

文章地址:https://www.zhihuclub.com/180687.shtml

关于作者: 智云科技

热门文章

网站地图