本博客系列是学习并发编程过程中的记录总结。由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅。

并发编程系列博客传送门


方法简介

wait方法

当一个线程调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起(进入waiting状态),直到发生下面几件事情之一才能返回:

  • 其他线程调用了该共享对象的notify()或者notifyAll()方法;
  • 其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回。

另外需要注意的是,如果调用wait()方法的线程没有事先获取该对象的监视器锁,则调用wait()方法时调用线程会抛出IllegalMonitorStateException异常。如果当前线程已经获取了锁资源,调用wait方法之后会释放这个锁资源,但是只会释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,则这些锁是不会被释放的

wait方法还有一个重载方法wait(long time),这个方法会等待time时间,如果在这个时间内没有其他线程来唤醒它的话,这个线程会自己唤醒继续获得执行机会。

notify方法

notify方法会唤醒等待对象监视器的单个线程,如果等待对象监视器的有多个线程,则选取其中一个线程进行唤醒,到底选择唤醒哪个线程是任意的,由CPU自己决定。如果没有再调用notify方法,其他阻塞的线程可能就永远得不到再执行的机会了。

此外,被唤醒的线程不能马上从wait方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回,也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行。

一个还需要注意的地方是,在共享变量上调用notifyAll()方法只会唤醒调用这个方法前调用了wait系列函数而被放入共享变量等待集合里面的线程。如果调用notifyAll()方法后一个线程调用了该共享变量的wait()方法而被放入阻塞集合,则该线程是不会被唤醒的

类似wait系列方法,只有当前线程获取到了共享变量的监视器锁后,才可以调用共享变量的notify()方法,否则会抛出IllegalMonitorStateException异常。

notify方法还有个兄弟方法notifyAll,这个方法会唤醒所有等待监视器对象的线程。

wait-notify模式的典型应用

wait-notify模式的一个典型应用就是可以实现生产者-消费者模式。让我印象很深是我毕业那年阿里巴巴校园招聘的一个笔试题:

有一个苹果箱,有10个人向这个箱子中每次随机放入一个苹果,有10个人每次随机从这个箱子中随机拿走一个苹果,同时需要满足箱子中的苹果总数不能超过50个。请用代码实现上面的场景(不能使用并发集合框架)

现在看来,这道题不就是为wait-notify模式量身打造的一道题目么。当时水平有限,又急急忙忙的,所以记得当时写的不太好。这边重新整理下这个代码


public class AppleBox { private int appleCount; public synchronized void putApple() {
while (appleCount >= 50) {
try {
//会释放锁
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
appleCount++;
String name = Thread.currentThread().getName();
System.out.println("[" + name + "]放入一个,当前盒子中苹果数:" + appleCount);
this.notifyAll();
} public synchronized void takeApple() {
while (appleCount <= 0) {
try {
//会释放锁
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
appleCount--;
String name = Thread.currentThread().getName();
System.out.println("[" + name + "]拿走一个,当前盒子中苹果数:" + appleCount);
this.notifyAll();
} private static class AppleTaker implements Runnable { private AppleBox appleBox; public AppleTaker(AppleBox appleBox) {
this.appleBox = appleBox;
} @Override
public void run() {
while (true) {
appleBox.takeApple();
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} private static class ApplePutter implements Runnable { private AppleBox appleBox; public ApplePutter(AppleBox appleBox) {
this.appleBox = appleBox;
} @Override
public void run() {
while (true) {
appleBox.putApple();
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} public static void main(String[] args) {
AppleBox appleBox = new AppleBox(); for (int i = 0; i < 20; i++) {
Thread t = new Thread(new ApplePutter(appleBox));
t.setName("ApplePutter:" + i);
t.start();
} for (int i = 0; i < 20; i++) {
Thread t = new Thread(new AppleTaker(appleBox));
t.setName("AppleTaker:" + i);
t.start();
} }
}

执行结果如下:


[ApplePutter:0]放入一个,当前盒子中苹果数:1
[ApplePutter:1]放入一个,当前盒子中苹果数:2
[ApplePutter:5]放入一个,当前盒子中苹果数:3
[ApplePutter:9]放入一个,当前盒子中苹果数:4
[ApplePutter:13]放入一个,当前盒子中苹果数:5
[ApplePutter:2]放入一个,当前盒子中苹果数:6
[ApplePutter:6]放入一个,当前盒子中苹果数:7
[ApplePutter:10]放入一个,当前盒子中苹果数:8
[ApplePutter:17]放入一个,当前盒子中苹果数:9
[ApplePutter:14]放入一个,当前盒子中苹果数:10
[ApplePutter:18]放入一个,当前盒子中苹果数:11
[ApplePutter:3]放入一个,当前盒子中苹果数:12
[ApplePutter:7]放入一个,当前盒子中苹果数:13
[ApplePutter:11]放入一个,当前盒子中苹果数:14
[ApplePutter:8]放入一个,当前盒子中苹果数:15
[ApplePutter:15]放入一个,当前盒子中苹果数:16
[ApplePutter:19]放入一个,当前盒子中苹果数:17
[ApplePutter:4]放入一个,当前盒子中苹果数:18
[AppleTaker:3]拿走一个,当前盒子中苹果数:17
[ApplePutter:12]放入一个,当前盒子中苹果数:18
[AppleTaker:1]拿走一个,当前盒子中苹果数:17
[AppleTaker:5]拿走一个,当前盒子中苹果数:16
[ApplePutter:16]放入一个,当前盒子中苹果数:17
[AppleTaker:0]拿走一个,当前盒子中苹果数:16
[AppleTaker:12]拿走一个,当前盒子中苹果数:15
[AppleTaker:8]拿走一个,当前盒子中苹果数:14
[AppleTaker:16]拿走一个,当前盒子中苹果数:13
[AppleTaker:7]拿走一个,当前盒子中苹果数:12
[AppleTaker:11]拿走一个,当前盒子中苹果数:11
[AppleTaker:19]拿走一个,当前盒子中苹果数:10
[AppleTaker:9]拿走一个,当前盒子中苹果数:9
[AppleTaker:13]拿走一个,当前盒子中苹果数:8
[AppleTaker:2]拿走一个,当前盒子中苹果数:7
[AppleTaker:6]拿走一个,当前盒子中苹果数:6
[AppleTaker:10]拿走一个,当前盒子中苹果数:5
[AppleTaker:14]拿走一个,当前盒子中苹果数:4
[AppleTaker:4]拿走一个,当前盒子中苹果数:3
[AppleTaker:15]拿走一个,当前盒子中苹果数:2
[AppleTaker:18]拿走一个,当前盒子中苹果数:1
[AppleTaker:17]拿走一个,当前盒子中苹果数:0
[ApplePutter:0]放入一个,当前盒子中苹果数:1
[ApplePutter:1]放入一个,当前盒子中苹果数:2
[ApplePutter:5]放入一个,当前盒子中苹果数:3
[ApplePutter:9]放入一个,当前盒子中苹果数:4
[ApplePutter:13]放入一个,当前盒子中苹果数:5
[ApplePutter:17]放入一个,当前盒子中苹果数:6
[ApplePutter:2]放入一个,当前盒子中苹果数:7
[ApplePutter:6]放入一个,当前盒子中苹果数:8
[ApplePutter:10]放入一个,当前盒子中苹果数:9
[ApplePutter:14]放入一个,当前盒子中苹果数:10
[ApplePutter:18]放入一个,当前盒子中苹果数:11
[ApplePutter:3]放入一个,当前盒子中苹果数:12
[ApplePutter:7]放入一个,当前盒子中苹果数:13
[ApplePutter:11]放入一个,当前盒子中苹果数:14
[ApplePutter:15]放入一个,当前盒子中苹果数:15
[ApplePutter:19]放入一个,当前盒子中苹果数:16
[AppleTaker:3]拿走一个,当前盒子中苹果数:15
[ApplePutter:4]放入一个,当前盒子中苹果数:16
[ApplePutter:8]放入一个,当前盒子中苹果数:17
[ApplePutter:12]放入一个,当前盒子中苹果数:18

**PS: 多线程编程中,最要的重要的两点是先抽象出共享变量是什么,任务类(Runner)是什么 **

wait-notify模式的经典写法

生产者和消费者的逻辑都可以统一抽象成以下几个步骤:

  • step1:获得对象的锁;
  • step2:循环判断是否需要进行生产活动,如果不需要进行生产就调用wait方法,暂停当前线程;如果需要进行生产活动,进行对应的生产活动;
  • step3:通知等待线程

伪代码如下:

synchronized(对象) {
//这边进行循环判断的原因是为了防止伪唤醒,也就是不是消费线程或者生产线程调用notify方法将waiting线程唤醒的
while(条件){
对象.wait();
}
//进行生产或者消费活动
doSomething();
对象.notifyAll();
}

【并发编程】Object的wait、notify和notifyAll方法的更多相关文章

  1. 多线程协作wait、notify、notifyAll方法简介理解使用 多线程中篇(十四)

    在锁与监视器中有对wait和notify以及notifyAll进行了简单介绍 所有对象都有一个与之关联的锁与监视器 wait和notify以及notifyAll之所以是Object的方法就是因为任何一 ...

  2. 关于java的wait、notify、notifyAll方法

    wait.notify.notifyAll 遇到的问题 之前开发打印机项目,因为需要使用多线程技术,当时并不怎么理解,一开始随意在方法体内使用wait.notify.notifyAll 方法导致出现了 ...

  3. Java线程和多线程(二)——对象中的wait,notify以及notifyAll方法

    Java对象中的wait,notify以及notifyAll方法 在Java的Object类中包含了3个final的方法,这三个方法允许线程来交流资源是否被锁定.这三个方法就是wait(),notif ...

  4. wait、notify和notifyAll方法学习

    wait.notify和notifyAll方法 wait() 方法会使该锁资源释放,然后线程进入等待WAITING状态,进入锁的waitset中,然后等待其他线程对锁资源调用notify方法或noti ...

  5. 【Java并发系列02】Object的wait()、notify()、notifyAll()方法使用

    一.前言 对于并发编程而言,除了Thread以外,对Object对象的wati和notify对象也应该深入了解其用法,虽然知识点不多. 二.线程安全基本知识 首先应该记住以下基本点,先背下来也无妨: ...

  6. Python并发编程之创建多线程的几种方法(二)

    大家好,并发编程 进入第二篇. 今天的内容会比较基础,主要是为了让新手也能无障碍地阅读,所以还是要再巩固下基础.学完了基础,你们也就能很顺畅地跟着我的思路理解以后的文章. 本文目录 学会使用函数创建多 ...

  7. python 并发编程 多线程 Thread对象的其他属性或方法

    介绍 Thread实例对象的方法 # isAlive(): 返回线程是否活动的. # getName(): 返回线程名. # setName(): 设置线程名. threading模块提供的一些方法: ...

  8. 使用Object的wait,notify,notifyAll做线程调度

    我们知道java中的所有类的祖先都是Object,Object类有四个个方法wait(),wait(long timeout),notify(),notifyAll(),这四个方法可以用来做线程的调度 ...

  9. 并发编程(六)Object类中线程相关的方法详解

    一.notify() 作用:唤醒一个正在等待该线程的锁的线程 PS : 唤醒的线程不会立即执行,它会与其他线程一起,争夺资源 /** * Object类的notify()和notifyAll()方法详 ...

随机推荐

  1. 12 Zabbix4.4.0系统sendEmail邮件报警优化

    点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 12 Zabbix4.4.0系统sendEmail邮件报警优化 接上一章节  Zabbix4.4. ...

  2. linux(CentOS release 6.5)环境搭建svn

    正文之前,说几句关于svn和git的闲话. 之前用的版本控制工具主要都是svn,随着时间的推移,git以其强大灵活的分支管理功能受到大众喜爱.尤其是多人同时开发时同一项目,且不同部分功能时,git的分 ...

  3. [考试反思]1107csp-s模拟测试104: 速度

    20分钟能做什么? 不粘排行榜,没意义,第一机房集体重启,我侥幸找回了两个文件才有分. 实际得分应该是70+100+60,第二机房rank1...放在第一机房就不知道了 T1:中间值 比较喜欢题解的第 ...

  4. CSPS模拟 84

    整场考试就一个字虚 真的啥也不会 T1 80很好打 可是100这鬼畜的数据范围...二分答案? 没做过蚯蚓跪..果然多刷题有好处.. 于是死在80分处 T2 56很好打 可是100这鬼畜....... ...

  5. 第5天,python之路

    已经过了两三天了,东西一点没来写,今天慢慢的写吧. 还是会按Alex Li的流程来学习,编写     写出自己的东西 常用的数据类型 简单的有,str ,int , 常用的:  列表list[], 字 ...

  6. 002.Kubernetes简单入门实例

    一 环境准备 1.1 基础环境 Kubernetes模式:单机版 系统环境:CentOS 7/172.24.9.157 部署方式:yum快速部署 其他设置:开启NTP.关闭防火墙及SELinux 二 ...

  7. jquery serialize()函数用法

    jquery serialize()函数用法<pre><html><head><script type="text/javascript" ...

  8. MySQL每个分类的前几条记录

    MySQL 获取所有分类和每个分类的前几条记录 比如有文章表 Article(Id,Category,InsertDate) 现在要用SQL找出每种类型中时间最新的前N个数据组成的集合 SELECT ...

  9. 一次shardingjdbc踩坑引起的胡思乱想

    项目里面的一个分表用到了sharding-jdbc 当时纠结过是用mycat还是用sharding-jdbc的, 但是最终还是用了sharding-jdbc, 原因如下: 1. mycat比较重, 相 ...

  10. JS、JQ相关小技巧积攒

    JS.JQ相关小技巧积攒,以备不时之需. 1.js 获取时间差:时间戳相减.new Date().getTime()  获得毫秒数,除以(1000*60*60*24) 获得天数. 2.重定向操作:页面 ...