《Java并发编程的艺术》读书笔记:等待/通知机制

看这本书之前,对wait和notify认识大概就是,调用wait的线程A堵塞之后,一旦另外有线程调用notify方法。线程A会立马从wait方法处返回。看完这本书后。发现自己的认识实在太肤浅了。。。。。

线程调用wait()后,会释放已经获得的锁。

同一时候进入Waiting状态,而非Blocked状态。唯独等待其它的线程调用notify()方法且释放锁之后。当前线程才会从wait()方法处返回。只发出通知是不够用的。还须要发出通知的线程释放锁。

然而notify()方法的调用并不意味着锁的释放。

贴出原书的demo,稍作了些改动。

/**
 *
 */
package chapter04;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 6-11
 */
public class WaitNotify {
	private static final SimpleDateFormat sdf  = new SimpleDateFormat("HH:mm:ss");
	static               boolean          flag = true;
	static               Object           lock = new Object();

	public static void main(String[] args) {
		Thread waitThread = new Thread(new Wait(), "WaitThread");
		waitThread.start();
		sleepSeconds(1);

		Thread notifyThread = new Thread(new Notify(), "NotifyThread");
		notifyThread.start();
	}

	static class Wait implements Runnable {
		public void run() {
			synchronized (lock) {
				// 当条件不满足时,继续wait,同一时候释放了lock的锁
				while (flag) {
					try {
						System.out.println(CuttentThreadName() + " flag is true. wait @ " + sdf.format(new Date()));
						lock.wait();
						System.out.println(CuttentThreadName() + " gained lock again. wait @ " + sdf.format(new Date()));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				// 条件满足时。完毕工作
				System.out.println(CuttentThreadName() + " flag is false. running @ " + sdf.format(new Date()));
			}
		}
	}

	private static String CuttentThreadName() {
		return Thread.currentThread().getName();
	}

	static class Notify implements Runnable {
		@Override
		public void run() {
			// 加锁,拥有lock的Monitor
			synchronized (lock) {
				// 获取lock的锁。然后进行通知,通知时不会释放lock的锁,
				// 直到当前线程释放了lock后,WaitThread才干从wait方法中返回
				System.out.println(CuttentThreadName() + " hold lock. notify @ " + sdf.format(new Date()));
				lock.notifyAll();
				flag = false;
				sleepSeconds(5);
				System.out.println(CuttentThreadName() + " is releasing lock  @ " + sdf.format(new Date()));
			}
			// 再次加锁
			synchronized (lock) {
				System.out.println(CuttentThreadName() + " hold lock again. sleep @ " + sdf.format(new Date()));
				sleepSeconds(5);
				System.out.println(CuttentThreadName() + " is releasing lock  @ " + sdf.format(new Date()));
			}
		}
	}

	private static void sleepSeconds(int timeout) {
		try {
			TimeUnit.SECONDS.sleep(timeout);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

输出结果为:

WaitThread flag is true. wait @ 20:58:32
NotifyThread hold lock. notify @ 20:58:33
NotifyThread is releasing lock  @ 20:58:38
NotifyThread hold lock again. sleep @ 20:58:38
NotifyThread is releasing lock  @ 20:58:43
WaitThread gained lock again. wait @ 20:58:43
WaitThread flag is false. running @ 20:58:43

通过输出结果,我们能够发现几个问题。

1.NotifyThread调用notifyAll()方法后,WaitThread并没有立马从wait()方法处返回。

由于这个时候NotifyThread并没有释放锁。

2.程序61行,NotifyThread第一次释放锁,然而WaitThread不争气,并没有抢到这把锁。依然处于Blocked状态。

3.直到67行。NotifyThread再一次释放锁。WaitThread获得了锁,这才从wait()处返回继续运行。

4.线程从wait()方法返回的前提是,获得synchronized须要的锁。

分析完原书的demo之后,是否有跟我一样的感觉,以前的认识是那么的too young too simple。

时间: 2024-07-30 03:34:41

《Java并发编程的艺术》读书笔记:等待/通知机制的相关文章

<java并发编程的艺术>读书笔记-第三章java内存模型(一)

一概述 本文属于<java并发编程的艺术>读书笔记系列,继续第三章java内存模型. 二重排序 2.1数据依赖性 如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性.数据依赖分下列三种类型: 名称 代码示例 说明 写后读 a = 1;b = a; 写一个变量之后,再读这个位置. 写后写 a = 1;a = 2; 写一个变量之后,再写这个变量. 读后写 a = b;b = 1; 读一个变量之后,再写这个变量. 上面三种情况,只要重排序两个操作的执行顺序,

synchronized的实现原理-java并发编程的艺术读书笔记

1.synchronized实现同步的基础 Java中的每个对象都是可以作为锁,具体有3种表现. 1.对于普通同步方法,锁是当前实例对象. 2.对于静态同步方法,锁是当前类的Class对象. 3.对于同步方法块,锁是Synchonized括号里面的配置对象. 当前一个线程试图访问同步代码块时,它首先必须得到锁,退出或者抛出异常时候必须释放锁.那么锁到底存在什么地方? 从JVM规范可以看到Synchonized在JVM里的实现原理,JVM基于进入和退出Monitor对象来实现方法同步和代码快同步,

《Java并发编程实战》读书笔记

Subsections 线程安全(Thread safety) 锁(lock) 共享对象 对象组合 基础构建模块 任务执行 取消和关闭 线程池的使用 性能与可伸缩性 并发程序的测试 显示锁 原子变量和非阻塞同步机制 一.线程安全(Thread safety) 无论何时,只要多于一个线程访问给定的状态变量.而且其中某个线程会写入该变量,此时必须使用同步来协助线程对该变量的访问. 线程安全是指多个线程在访问一个类时,如果不需要额外的同步,这个类的行为仍然是正确的. 线程安全的实例: (1).一个无状

《java并发编程实战》读书笔记2--对象的共享,可见性,安全发布,线程封闭,不变性

这章的主要内容是:如何共享和发布对象,从而使它们能够安全地由多个线程同时访问. 内存的可见性 确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化. 上面的程序中NoVisibility可能会持续循环下去,因为读线程可能永远都看不到ready的值.一种更奇怪的现象是NoVisibility可能会输出0,因为读线程可能看到了写入ready的值,但却没有看到之后写入number的值,这种现象被称为"重排序".多线程之指令重排序 失效数据 简而言之就是在缺乏同步的程序中可能会读取到

《java并发编程实战》读书笔记4--基础构建模块,java中的同步容器类&amp;并发容器类&amp;同步工具类,消费者模式

上一章说道委托是创建线程安全类的一个最有效策略,只需让现有的线程安全的类管理所有的状态即可.那么这章便说的是怎么利用java平台类库的并发基础构建模块呢? 5.1 同步容器类 包括Vector和Hashtable,此外还包括在JDK1.2中添加的一些功能相似的类,这些同步的封装器类由Collections.synchronizedXxx等工厂方法创建的.这些类实现线程安全的方式是:将他们的状态封装起来,并对每个共有方法都进行同步,使得每次只能有一个线程能访问容器的状态. 关于java中的Vect

《Java并发编程实战》读书笔记(一)----- 简介

简史 早期的计算机中不包含操作系统,从头至尾都只执行一个程序,并且这个程序能访问计算机所有资源.随着计算机发展,操作系统的出现,使得计算机可以同时运行多个程序,并且每程序都在单独的进程内运行.为什么要这么做呢?主要基于以下原因: 1.资源利用率 程序在等待时计算机可以执行另一个程序,同一个资源可以被不同程序使用,这无疑提高了资源的利用率.2.公平性 不同的用户与程序对计算机有相同的使用权,通过时间分片每个程序共享资源,而不是等待上一个程序运行结束再启动下一个程序.3.便利性 每个程序单独运行,必

《java并发编程实战》读书笔记6--取消与关闭

这章的主要内容是关于如何使任务和线程安全,快速,可靠的停止下来. 7.1 任务取消 在Java中没有一种安全的抢占方式来停止线程,但是可以使用一些协作机制,比如: 让素数生成器运行1秒后取消(并不会刚好在运行1秒后停止,因为在请求取消的时刻和run方法中循环执行下一次检查之间可能存在延迟): -7.1.1 中断 上面的取消方法有个重要的问题是:如果任务中调用了一个阻塞方法,例如BlockingQueue.put,那么任务可能永远不会检查取消标志,因此永远不会结束.比如: 前面第五章曾提到,一些特

并发编程的艺术读书笔记-第四章线程基础

线程6种状态 1)new:初始状态,线程被构建,但还没有调用start()方法. 2)runnable:运行状态,java中将系统的运行状态和就绪状态统称为运行状态 3)blocked:阻塞状态,表示线程阻塞于锁 (synchronized) 4)waiting:等待状态,进入该状态表示该线程需要等待其它线程作出一些动作 (中断或者通知 , object.wait()) 5)TIME_WAITING:超时等待状态,该状态不同于waiting,他是可以在指定时间自动返回的  sleep导致 6)t

《java并发编程实战》读书笔记9--并发程序的测试

第12章 并发程序的测试 大致分为两类:安全性测试和活跃性测试 12.1 正确性测试 找出需要检查的不变性条件和后验条件.接下来将构建一组测试用例来测试一个有界缓存.程序清单12-1给出了BoundedBuffer的实现,其中使用Semaphore来实现缓存的有界属性和阻塞行为. BoundedBuffer实现了一个固定长度的队列,其中定义了可阻塞的put和take方法,并通过两个计数信号量进行控制.(实际情况中如果需要一个有界缓存,应该直接使用ArrayBlockingQueue或者Linke

《java并发编程实战》读书笔记5--任务执行, Executor框架

6.1 在线程中执行任务 第一步要找出清晰的任务边界.大多数服务器应用程序都提供了一种自然的任务边界选择方式:以独立的请求为边界. -6.6.1 串行地执行任务 最简单的任务调度策略是在单个线程中串行地执行各项任务. 虽然简单明了,但是每次只能处理一个请求.当服务器正在处理请求时,新到来的连接必须等待直到请求处理完成,然后服务器将再次调用accept. -6.1.2 显示地为任务创建线程 -6.1.3 无限制创建线程的不足 (1) 线程生命周期的开销非常高 (2) 资源消耗 (3) 稳定性: 在