`
20386053
  • 浏览: 433381 次
文章分类
社区版块
存档分类
最新评论

Java并发编程学习笔记之十一:线程间通信中notify通知的遗漏(含代码)

 
阅读更多

转载请注明出处:http://blog.csdn.net/ns_code/article/details/17228213

notify通知的遗漏很容易理解,即threadA还没开始wait的时候,threadB已经notify了,这样,threadB通知是没有任何响应的,当threadB退出synchronized代码块后,threadA再开始wait,便会一直阻塞等待,直到被别的线程打断。

遗漏通知的代码

下面给出一段代码演示通知是如何遗漏的,如下:

public class MissedNotify extends Object {
	private Object proceedLock;

	public MissedNotify() {
		print("in MissedNotify()");
		proceedLock = new Object();
	}

	public void waitToProceed() throws InterruptedException {
		print("in waitToProceed() - entered");

		synchronized ( proceedLock ) {
			print("in waitToProceed() - about to wait()");
			proceedLock.wait();
			print("in waitToProceed() - back from wait()");
		}

		print("in waitToProceed() - leaving");
	}

	public void proceed() {
		print("in proceed() - entered");

		synchronized ( proceedLock ) {
			print("in proceed() - about to notifyAll()");
			proceedLock.notifyAll();
			print("in proceed() - back from notifyAll()");
		}

		print("in proceed() - leaving");
	}

	private static void print(String msg) {
		String name = Thread.currentThread().getName();
		System.out.println(name + ": " + msg);
	}

	public static void main(String[] args) {
		final MissedNotify mn = new MissedNotify();

		Runnable runA = new Runnable() {
				public void run() {
					try {
						//休眠1000ms,大于runB中的500ms,
						//是为了后调用waitToProceed,从而先notifyAll,后wait,
						//从而造成通知的遗漏
						Thread.sleep(1000);
						mn.waitToProceed();
					} catch ( InterruptedException x ) {
						x.printStackTrace();
					}
				}
			};

		Thread threadA = new Thread(runA, "threadA");
		threadA.start();

		Runnable runB = new Runnable() {
				public void run() {
					try {
						//休眠500ms,小于runA中的1000ms,
						//是为了先调用proceed,从而先notifyAll,后wait,
						//从而造成通知的遗漏
						Thread.sleep(500);
						mn.proceed();
					} catch ( InterruptedException x ) {
						x.printStackTrace();
					}
				}
			};

		Thread threadB = new Thread(runB, "threadB");
		threadB.start();

		try { 
			Thread.sleep(10000);
		} catch ( InterruptedException x ) {}

		//试图打断wait阻塞
		print("about to invoke interrupt() on threadA");
		threadA.interrupt();
	}
}


执行结果如下:

分析:由于threadB在执行mn.proceed()之前只休眠了500ms,而threadA在执行mn.waitToProceed()之前休眠了1000ms,因此,threadB会先苏醒,继而执行mn.proceed(),获取到proceedLock的对象锁,继而执行其中的notifyAll(),当退出proceed()方法中的synchronized代码块时,threadA才有机会获取proceedLock的对象锁,继而执行其中的wait()方法,但此时notifyAll()方法已经执行完毕,threadA便漏掉了threadB的通知,便会阻塞下去。后面主线程休眠10秒后,尝试中断threadA线程,使其抛出InterruptedException。

修正后的代码

为了修正MissedNotify,需要添加一个boolean指示变量,该变量只能在同步代码块内部访问和修改。修改后的代码如下:

public class MissedNotifyFix extends Object {
	private Object proceedLock;
	//该标志位用来指示线程是否需要等待
	private boolean okToProceed;

	public MissedNotifyFix() {
		print("in MissedNotify()");
		proceedLock = new Object();
		//先设置为false
		okToProceed = false;
	}

	public void waitToProceed() throws InterruptedException {
		print("in waitToProceed() - entered");

		synchronized ( proceedLock ) {
			print("in waitToProceed() - entered sync block");
			//while循环判断,这里不用if的原因是为了防止早期通知
			while ( okToProceed == false ) {
				print("in waitToProceed() - about to wait()");
				proceedLock.wait();
				print("in waitToProceed() - back from wait()");
			}

			print("in waitToProceed() - leaving sync block");
		}

		print("in waitToProceed() - leaving");
	}

	public void proceed() {
		print("in proceed() - entered");

		synchronized ( proceedLock ) {
			print("in proceed() - entered sync block");
			//通知之前,将其设置为true,这样即使出现通知遗漏的情况,也不会使线程在wait出阻塞
			okToProceed = true;
			print("in proceed() - changed okToProceed to true");
			proceedLock.notifyAll();
			print("in proceed() - just did notifyAll()");

			print("in proceed() - leaving sync block");
		}

		print("in proceed() - leaving");
	}

	private static void print(String msg) {
		String name = Thread.currentThread().getName();
		System.out.println(name + ": " + msg);
	}

	public static void main(String[] args) {
		final MissedNotifyFix mnf = new MissedNotifyFix();

		Runnable runA = new Runnable() {
				public void run() {
					try {
						//休眠1000ms,大于runB中的500ms,
						//是为了后调用waitToProceed,从而先notifyAll,后wait,
						Thread.sleep(1000);
						mnf.waitToProceed();
					} catch ( InterruptedException x ) {
						x.printStackTrace();
					}
				}
			};

		Thread threadA = new Thread(runA, "threadA");
		threadA.start();

		Runnable runB = new Runnable() {
				public void run() {
					try {
						//休眠500ms,小于runA中的1000ms,
						//是为了先调用proceed,从而先notifyAll,后wait,
						Thread.sleep(500);
						mnf.proceed();
					} catch ( InterruptedException x ) {
						x.printStackTrace();
					}
				}
			};

		Thread threadB = new Thread(runB, "threadB");
		threadB.start();

		try { 
			Thread.sleep(10000);
		} catch ( InterruptedException x ) {}

		print("about to invoke interrupt() on threadA");
		threadA.interrupt();
	}
}

执行结果如下:

注意代码中加了注释的部分,在threadB进行通知之前,先将okToProceed置为true,这样如果threadA将通知遗漏,那么就不会进入while循环,也便不会执行wait方法,线程也就不会阻塞。如果通知没有被遗漏,wait方法返回后,okToProceed已经被置为true,下次while循环判断条件不成立,便会退出循环。

这样,通过标志位和wait、notifyAll的配合使用,便避免了通知遗漏而造成的阻塞问题。

总结:在使用线程的等待/通知机制时,一般都要配合一个boolean变量值(或者其他能够判断真假的条件),在notify之前改变该boolean变量的值,让wait返回后能够退出while循环(一般都要在wait方法外围加一层while循环,以防止早期通知),或在通知被遗漏后,不会被阻塞在wait方法处。这样便保证了程序的正确性。

分享到:
评论

相关推荐

    java并发编程:线程基础

    本资源致力于向您介绍 Java 并发编程中的线程基础,涵盖了多线程编程的核心概念、线程的创建和管理,以及线程间通信的基本方法。通过深入学习,您将建立扎实的多线程编程基础,能够更好地理解和应用多线程编程。 多...

    龙果 java并发编程原理实战

    第4节学习并发的四个阶段并推荐学习并发的资料 [免费观看] 00:09:13分钟 | 第5节线程的状态以及各状态之间的转换详解00:21:56分钟 | 第6节线程的初始化,中断以及其源码讲解00:21:26分钟 | 第7节多种创建线程的...

    Java并发编程基础.pdf

    线程同步与通信:掌握Java中的同步机制,如synchronized关键字、wait()和notify()方法,以及更高级的并发工具如ReentrantLock、Condition等。了解线程间的通信方式,如共享内存、消息传递等。 并发集合:熟悉Java...

    java并发编程

    本书全面介绍了如何使用Java 2平台进行并发编程,较上一版新增和扩展的内容包括:, ·存储模型 ·取消 ·可移植的并行编程 ·实现...贯串全书的大量示例代码详细地阐述了在讨论中所涉及到的并发编程理念的细微之处。

    Java并发编程:设计原则与模式(第二版)

    读者将通过使用java.lang.thread类、synchronized和volatile关键字,以及wait、notify和notifyall方法,学习如何初始化、控制和协调并发操作。此外,本书还提供了有关并发编程的全方位的详细内容,例如限制和同步、...

    龙果java并发编程完整视频

    第4节学习并发的四个阶段并推荐学习并发的资料 [免费观看] 00:09:13分钟 | 第5节线程的状态以及各状态之间的转换详解00:21:56分钟 | 第6节线程的初始化,中断以及其源码讲解00:21:26分钟 | 第7节多种创建线程的...

    Java 并发编程原理与实战视频

    第4节学习并发的四个阶段并推荐学习并发的资料 [免费观看] 00:09:13分钟 | 第5节线程的状态以及各状态之间的转换详解00:21:56分钟 | 第6节线程的初始化,中断以及其源码讲解00:21:26分钟 | 第7节多种创建线程的...

    java高并发相关知识点.docx

    Java高并发相关知识点包括: 线程:Java多线程的实现方式,包括继承Thread类和实现Runnable接口。 锁:Java中的锁机制,包括...线程间通信:Java中的线程间通信,包括wait()、notify()、notifyAll()等方法。

    Java并发编程原理与实战

    线程之间的通信之wait notify.mp4 通过生产者消费者模型理解等待唤醒机制.mp4 Condition的使用及原理解析.mp4 使用Condition重写waitnotify案例并实现一个有界队列.mp4 深入解析Condition源码.mp4 实战:简易数据...

    汪文君高并发编程实战视频资源下载.txt

    │ 高并发编程第一阶段24讲、线程间通信快速入门,使用wait和notify进行线程间的数据通信.mp4 │ 高并发编程第一阶段25讲、多Produce多Consume之间的通讯导致出现程序假死的原因分析.mp4 │ 高并发编程第一阶段26...

    Java 线程间通信,生产者与消费者模型

    使用wait()和notify()实现的生产者与消费者模型,可以了解如何使用wait()和notify()进行线程间通信。(上一次上传的代码有一个问题没有考虑到,这次修补了——CSDN没法撤销资源,只能再上传了)

    java中的并发和多线程编程中文版

    读者将通过使用java.lang.thread类、synchronized和volatile关键字,以及wait、notify和notifyall方法,学习如何初始化、控制和协调并发操作。此外,本书还提供了有关并发编程的全方位的详细内容,例如限制和同步、...

    多线程系列相关的技术要点

    1. Java多线程学习(一)Java多线程入门 2. Java多线程学习(二)synchronized关键字(1) 3. Java多线程学习(二)...8. Java多线程学习(七)并发编程中一些问题 9. Java多线程学习(八)线程池与Executor 框架

    汪文君高并发编程实战视频资源全集

    │ 高并发编程第一阶段24讲、线程间通信快速入门,使用wait和notify进行线程间的数据通信.mp4 │ 高并发编程第一阶段25讲、多Produce多Consume之间的通讯导致出现程序假死的原因分析.mp4 │ 高并发编程第一阶段26...

    41.线程间的通信-wait与notify-只唤醒一个线程或所有线程.mp4

    在学习Java过程中,自己收集了很多的Java的学习资料,分享给大家,有需要的欢迎下载,希望对大家有用,一起学习,一起进步。

    UNIX网络编程 卷2:进程间通信

     4.9 对比迭代服务器与并发服务器 50  4.10 字节流与消息 51  4.11 管道和FIFO限制 55  4.12 小结 56  习题 57  第5章 Posix消息队列 58  5.1 概述 58  5.2 mq_open、mq_close和mq_unlink函数 59  5.3 mq_...

    java线程学习笔记

    2.3 线程本地存储(Java.lang.ThreadLocal) 15 2.4 线程阻塞 17 2.4.1 调用sleep(millisecond)使任务进入休眠状态 17 2.4.2 等待输出与输入 17 2.4.3 对象锁不可用 17 2.4.4 通过wait()使线程挂起。 17 2.5 线程...

Global site tag (gtag.js) - Google Analytics