如果线程在等待时接到通知,但线程等待的条件还不满足,此时,线程接到的就是早期通知,如果条件满足的时间很短,但很快又改变了,而变得不再满足,这时也将发生早期通知。这种现象听起来很奇怪,下面通过一个示例程序来说明问题。

​ 很简单,两个线程等待删除List中的元素,同时另外一个线程正要向其中添加项目。代码如下:

import java.util.*;  

public class EarlyNotify extends Object {
private List list; public EarlyNotify() {
list = Collections.synchronizedList(new LinkedList());
} public String removeItem() throws InterruptedException {
print("in removeItem() - entering"); synchronized ( list ) {
if ( list.isEmpty() ) { //这里用if语句会发生危险
print("in removeItem() - about to wait()");
list.wait();
print("in removeItem() - done with wait()");
} //删除元素
String item = (String) list.remove(0); print("in removeItem() - leaving");
return item;
}
} public void addItem(String item) {
print("in addItem() - entering");
synchronized ( list ) {
//添加元素
list.add(item);
print("in addItem() - just added: '" + item + "'"); //添加后,通知所有线程
list.notifyAll();
print("in addItem() - just notified");
}
print("in addItem() - leaving");
} private static void print(String msg) {
String name = Thread.currentThread().getName();
System.out.println(name + ": " + msg);
} public static void main(String[] args) {
final EarlyNotify en = new EarlyNotify(); Runnable runA = new Runnable() {
public void run() {
try {
String item = en.removeItem();
print("in run() - returned: '" +
item + "'");
} catch ( InterruptedException ix ) {
print("interrupted!");
} catch ( Exception x ) {
print("threw an Exception!!!\n" + x);
}
}
}; Runnable runB = new Runnable() {
public void run() {
en.addItem("Hello!");
}
}; try {
//启动第一个删除元素的线程
Thread threadA1 = new Thread(runA, "threadA1");
threadA1.start(); Thread.sleep(500); //启动第二个删除元素的线程
Thread threadA2 = new Thread(runA, "threadA2");
threadA2.start(); Thread.sleep(500);
//启动增加元素的线程
Thread threadB = new Thread(runB, "threadB");
threadB.start(); Thread.sleep(10000); // wait 10 seconds threadA1.interrupt();
threadA2.interrupt();
} catch ( InterruptedException x ) {}
}
}

执行结果如下:

​ 分析:首先启动threadA1,threadA1在removeItem()中调用wait(),从而释放list上的对象锁。再过500ms,启动threadA2,threadA2调用removeItem(),获取list上的对象锁,也发现列表为空,从而在wait()方法处阻塞,释放list上的对象锁。再过500ms后,启动threadB,并调用addItem,获得list上的对象锁,并在list中添加一个元素,同时用notifyAll通知所有线程。

​ threadA1和threadA2都从wait()返回,等待获取list对象上的对象锁,并试图从列表中删除添加的元素,这就会产生麻烦,只有其中一个操作能成功。假设threadA1获取了list上的对象锁,并删除元素成功,在退出synchronized代码块时,它便会释放list上的对象锁,此时threadA2便会获取list上的对象锁,会继续删除list中的元素,但是list已经为空了,这便会抛出IndexOutOfBoundsException。

​ 要避免以上问题只需将wait外围的if语句改为while循环即可,这样当list为空时,线程便会继续等待,而不会继续去执行删除list中元素的代码。

​ 修改后的执行结果如下:

总结:在使用线程的等待/通知机制时,一般都要在while循环中调用wait()方法,满足条件时,才让while循环退出,这样一般也要配合使用一个boolean变量(或其他能判断真假的条件,如本文中的list.isEmpty()),满足while循环的条件时,进入while循环,执行wait()方法,不满足while循环的条件时,跳出循环,执行后面的代码。

【Java并发编程】之十二:线程间通信中notifyAll造成的早期通知问题的更多相关文章

  1. 转:【Java并发编程】之十二:线程间通信中notifyAll造成的早期通知问题(含代码)

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17229601 如果线程在等待时接到通知,但线程等待的条件还不满足,此时,线程接到的就是早期 ...

  2. Java并发编程的艺术(六)——线程间的通信

    多条线程之间有时需要数据交互,下面介绍五种线程间数据交互的方式,他们的使用场景各有不同. 1. volatile.synchronized关键字 PS:关于volatile的详细介绍请移步至:Java ...

  3. 【JAVA并发第三篇】线程间通信

    线程间的通信 JVM在运行时会将自己管理的内存区域,划分为不同的数据区,称为运行时数据区.每个线程都有自己私有的内存空间,如下图示: Java线程按照自己虚拟机栈中的方法代码一步一步的执行下去,在这一 ...

  4. Java多线程编程(三)线程间通信

    线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时 ...

  5. Java并发编程(八)线程间协作(上)

    多线程并发执行时,不同的线程执行的内容之间可能存在一些依赖关系,比如线程一执行a()方法和c()方法,线程二执行b()方法,方法a()必须在方法b()之前执行,而方法c()必须在方法b()之后执行.这 ...

  6. Java并发编程(十三)线程间协作的两种方式:wait、notify、notifyAll和Condition

    在现实中,需要线程之间的协作.比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权.因为生产者如果 ...

  7. Java并发编程(九)线程间协作(下)

    上篇我们讲了使用wait()和notify()使线程间实现合作,这种方式很直接也很灵活,但是使用之前需要获取对象的锁,notify()调用的次数如果小于等待线程的数量就会导致有的线程会一直等待下去.这 ...

  8. java并发编程(十一)线程间的通信notify通知的遗漏

    notify通知的遗漏很容易理解,即threadA还没开始wait的时候,threadB已经notify了,这样,threadB通知是没有任何响应的,当threadB退出synchronized代码块 ...

  9. Java并发编程(十二)线程安全性的委托

    在组合对象中如果每个组件都已经是线程安全的,是否需要再加一个额外的"线程安全层",需要视情况而定. final可以修饰未复制的属性,只要在静态代码块或者构造函数中赋值了即可. 独立 ...

随机推荐

  1. node.js学习笔记——前序

    一.什么是node.js 简单的说 Node.js 就是运行在服务端的 JavaScript. Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台. Node.js是一 ...

  2. c#指针和寻址运算

    一.指针和寻址运算 指针格式:<类型>*<变量> 寻址格式:&<变量> 以下程序的运行结果为   注意:每次运行程序时第一行显示的地址都不会一样. usin ...

  3. python 另一种打开文章的方式——codecs

    通常我们使用python打开文件都是 open(‘beijing.txt’)或者是 with open(‘beijing.txt’)as f 那么今天来给你带来一个新的文档打开方式 python的co ...

  4. nginx解析漏洞,配置不当,目录遍历漏洞环境搭建、漏洞复现

    nginx解析漏洞,配置不当,目录遍历漏洞复现 1.Ubuntu14.04安装nginx-php5-fpm 安装了nginx,需要安装以下依赖 sudo apt-get install libpcre ...

  5. MySQL5.7(二)数据库的基本操作

    登录MySQL数据库 格式:mysql -u 用户名 -h 主机名或IP地址  -P 端口号 -p 密码

  6. TPO-23 C1 Post a student announcement

    第 1 段 1.Listen to a conversation between a student and the director of campus activities. 请听一段学生与校园活 ...

  7. Python输出格式全总结

    输入输出 有几种方法可以显示程序的输出:数据可以以人类可读的形式打印出来,或者写入文件以供将来使用.本章将讨论一些可能性. 更漂亮的输出格式 到目前为止,我们遇到了两种写入值的方法:表达式语句 和 p ...

  8. 文字排版 - bootStrap4常用CSS笔记

    [文字常用标签]   <h1>.<h2>.<h3>.<h4>.<h5>.<h6> 标题类标签,h1字体最大以次类推 <sm ...

  9. 高可用注册中心 ->Spring Cloud Eureka

    在微服务架构这样的分布式环境中,我们需要充分考虑发生故障的情况, 所以在生产 环境中必须对各个组件进行高可用部署, 对于微服务如此, 对于服务注册中心也一样. 但 是到本节为止,我们一直都在使用单节点 ...

  10. 基于python的scrapy框架爬取豆瓣电影及其可视化

    1.Scrapy框架介绍 主要介绍,spiders,engine,scheduler,downloader,Item pipeline scrapy常见命令如下: 对应在scrapy文件中有,自己增加 ...