1 前言

本篇文章默认大家对synchronizedReentrantLock有一定了解。

1.1 先来段代码放松一下

下面一段简单的代码,主要是通过3个线程对count进行累计来进行模拟多线程的场景。

/**
* zhongxianyao
*/
public class Test {
private static final int N = 3;
private int count = 0; public void doSomething() {
// 实际业务中,这里可能是远程获取数据之类的耗时操作
for (int i=0; i<1000_000; i++) {
synchronized (this) {
count ++;
}
}
} public static void main(String[] args) throws Exception {
Test test = new Test();
for (int i=0; i<N; i++) {
Runnable runnable = () -> test.doSomething();
new Thread(runnable).start();
} Thread.sleep(1000);
System.out.println(test.count);
} }

在多线程编程中,一旦调用start()后,什么时候真正分配CPU时间片运行是不确定的,运行多久也是不确定的,所以有时候可能根据经验,预估一下程序的运行时间,然后进行sleep,最后获取结果。但这种方式太low了,有没有那么一种方式,当程序获取到结果后进行通知呢?下面将引出今天要讲的等待/通知机制。


2 Object wait()/notify()

2.1 一段入门代码

先来一段代码看一下wait()/notify()的基本用法

/**
* zhongxianyao
*/
public class Test { private static final int N = 3;
private int count = 0;
private int finishCount = 0; public void doSomething() {
for (int i=0; i<1000_000; i++) {
synchronized (this) {
count ++;
}
}
synchronized (this) {
finishCount ++;
notify();
}
} public static void main(String[] args) {
Test test = new Test();
for (int i=0; i<N; i++) {
Runnable runnable = () -> test.doSomething();
new Thread(runnable).start();
} synchronized (test) {
try {
while (test.finishCount != N) {
test.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
} System.out.println(test.count);
} }

结果输出3000000,结果是正确,是自己想要的。

2.2 问题三连击

a.为什么官方说wait() 要放在while里面?

接口描述如下

As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:
synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}

翻译一下:在一个论点版本中,中断跟虚假唤醒是可能,所以这个方法应始终放在一个循环中。

加上一句自己的解释:一般在项目中,一个线程不可能无缘无故等待,总是需要在某种条件下进行等待,而且其他线程唤醒这个线程的时候,可能用的是notifyAll(),数据被其他线程消费了,这里需要在判断一下是否满足特定的条件再继续运行。

b.为什么wait()必须在同步方法/代码块中调用?

解释1:wait()本身设计的逻辑就是在释放锁进行等待,如果没有获取锁,谈何释放。

解释2:通常在wait()的方法前面都会有while语句的判断,在这两条语句中会有时间间隔,可能会破坏程序,需要加上synchronized同步代码块来保证原子操作。

c.为什么wait(), notify() 和 notifyAll()是定义在Object里面而不是在Thread里面?

因为wait()等方法都是锁级别操作,再者Java提供的锁都是对象级别的而不是线程级别的,每个对象都有锁。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。

2.3 wait(long timeout)

在上面的例子中,如果notify();那行代码删除,wait()改为wait(100),如下,那么程序是否可以获取到正确的结果呢?

/**
* zhongxianyao
*/
public class Test { private static final int N = 3;
private int count = 0;
private int finishCount = 0; public void doSomething() {
for (int i=0; i<1000_000; i++) {
synchronized (this) {
count ++;
}
}
synchronized (this) {
finishCount ++;
//notify();
}
} public static void main(String[] args) {
Test test = new Test();
for (int i=0; i<N; i++) {
Runnable runnable = () -> test.doSomething();
new Thread(runnable).start();
} synchronized (test) {
try {
while (test.finishCount != N) {
test.wait(100);
}
} catch (Exception e) {
e.printStackTrace();
}
} System.out.println(test.count);
} }

运行结果是3000000,是正确的结果,看了一下文档,发现这个字段跟直觉理解的不一样,直觉告诉我,这个是最长等多久,等太久了就InterruptedException,结果不是。这个方法设置的时间,是说等待多久就唤醒自己。


3 Condition await()/signal()

3.1 用Condition进行替换

下面的代码,把前一个例子中的synchronized代码块,换成了lock()/unlock,notify()换成了condition.signal(),wait()换成了condition.await()。运行结果也是正确的。

/**
* zhongxianyao
*/
public class Test { private static final int N = 3;
private int count = 0;
private int finishCount = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition(); public void doSomething() {
for (int i=0; i<1000_000; i++) {
synchronized (this) {
count ++;
}
}
lock.lock();
finishCount ++;
if (finishCount == N) {
condition.signal();
}
lock.unlock();
} public static void main(String[] args) {
Test test = new Test();
for (int i=0; i<N; i++) {
Runnable runnable = () -> test.doSomething();
new Thread(runnable).start();
} test.lock.lock();
try {
test.condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
test.lock.unlock();
} System.out.println(test.count);
} }

3.2 signal()方法后不建议添加逻辑

public class ConditionTest {

    public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition(); new Thread(() -> {
try {
long time = System.currentTimeMillis();
lock.lock();
System.out.println("await start");
condition.await();
System.out.println("await end " + (System.currentTimeMillis() - time) + "ms");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "Thread-await").start(); try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} new Thread(() -> {
try {
lock.lock();
System.out.println("signal start");
TimeUnit.SECONDS.sleep(5);
condition.signal();
System.out.println("signal end");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("signal unlock");
}
}, "Thread-signal").start(); }
}

多次运行,结果都是一样的,如下:

await start
signal start
signal end
signal unlock
await end 5005ms

从运行结果可以看出,await()后,锁就释放了,但signal()后,锁不释放,一定要在unlock()之后,锁才释放,await()才会往下执行。

既然唤醒了其他线程,又不释放锁,可以调整唤醒的时机。一般在实际代码中,也是不建议signal()方法后添加逻辑,应该直接释放锁。

同理,上面的notify()也是在synchronized代码块结束后,wait()后面的语句才能真正执行。

3.3 boolean await(long time, TimeUnit unit)

把上面的condition.await()改为condition.await(1, TimeUnit.SECONDS),然后获取返回值,运行结果返回的是false

这个时候,如果把TimeUnit.SECONDS.sleep(5)condition.signal()这两行代码顺序调换一下,那么await的返回值就是true

再看到官方文档对这个返回值的描述,如下

{@code false} if the waiting time detectably elapsed
before return from the method, else {@code true}

翻译过来,大致意思就是“如果等待时间可以在方法返回之前检测到返回false,否则返回true”。但实际测试结果却是await()被唤醒的时候,而不是方法返回的时候。


4 区别

  • Object wait() notify() 搭配synchronized使用
  • Condition await() signal() 搭配Lock使用
  • Object notify() 是随机唤醒一个
  • Condition signal() 是唤醒第一个await()的线程
  • Object wait()有虚假唤醒,而Condition await() 没有

5 参考文档

Java并发之等待/通知机制的更多相关文章

  1. 【Java并发基础】使用“等待—通知”机制优化死锁中占用且等待解决方案

    前言 在前篇介绍死锁的文章中,我们破坏等待占用且等待条件时,用了一个死循环来获取两个账本对象. // 一次性申请转出账户和转入账户,直到成功 while(!actr.apply(this, targe ...

  2. 二 Java利用等待/通知机制实现一个线程池

    接着上一篇博客的 一Java线程的等待/通知模型 ,没有看过的建议先看一下.下面我们用等待通知机制来实现一个线程池 线程的任务就以打印一行文本来模拟耗时的任务.主要代码如下: 1  定义一个任务的接口 ...

  3. Java Concurrency - wait & notify, 等待通知机制

    生产者消费者问题是一个常见的多线程同步案例:一组生产者线程和一组消费者线程共享一个初始状态为空.大小为 N 的缓冲区.只有当缓冲区没满的时候,生产者才能把消息放入缓冲区,否则必须等待:只有缓冲区不空的 ...

  4. java多线程系列(三)---等待通知机制

    等待通知机制 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理解 ...

  5. Java多线程之三volatile与等待通知机制示例

    原子性,可见性与有序性 在多线程中,线程同步的时候一般需要考虑原子性,可见性与有序性 原子性 原子性定义:一个操作或者多个操作在执行过程中要么全部执行完成,要么全部都不执行,不存在执行一部分的情况. ...

  6. Java并发编程,Condition的await和signal等待通知机制

    Condition简介 Object类是Java中所有类的父类, 在线程间实现通信的往往会应用到Object的几个方法: wait(),wait(long timeout),wait(long tim ...

  7. 《java多线程编程核心技术》不使用等待通知机制 实现线程间通信的 疑问分析

    不使用等待通知机制 实现线程间通信的 疑问分析 2018年04月03日 17:15:08       ayf 阅读数:33 编辑 <java多线程编程核心技术>一书第三章开头,有如下案例: ...

  8. Java并发读书笔记:线程通信之等待通知机制

    目录 synchronized 与 volatile 等待/通知机制 等待 通知 面试常问的几个问题 sleep方法和wait方法的区别 关于放弃对象监视器 在并发编程中,保证线程同步,从而实现线程之 ...

  9. Java并发编程(04):线程间通信,等待/通知机制

    本文源码:GitHub·点这里 || GitEE·点这里 一.概念简介 1.线程通信 在操作系统中,线程是个独立的个体,但是在线程执行过程中,如果处理同一个业务逻辑,可能会产生资源争抢,导致并发问题, ...

随机推荐

  1. JQuery系列(6) - jQuery设计思想

    jQuery是目前使用最广泛的javascript函数库. 据统计,全世界排名前100万的网站,有46%使用jQuery,远远超过其他库.微软公司甚至把jQuery作为他们的官方库. JQuery设计 ...

  2. GeoIP简介与资源,定位经纬度,获取用户IP

    所谓GeoIP,就是通过来访者的IP,定位他的经纬度,国家/地区,省市,甚至街道等位置信息.这里面的技术不算难题,关键在于有个精准的数据库.有了准确的数据源就奇货可居赚点小钱,可是发扬合作精神,集体贡 ...

  3. Java Array、List、Set互相转化

    https://blog.csdn.net/u014532901/article/details/78820124

  4. 在WinDbg中显示和搜索std::map内容

    我们希望在WinDbg中自动显示.搜索和过滤std::map对象.std::vectors的脚本相对简单,因为vectors中数据的平面结构:map是更复杂的野兽.具体地说,Visual C++ ST ...

  5. MySQL 8.0.18 InnoDB Cluster 主从(MGR)完整安装配置

    提示: MySQL InnoDB Cluster底层依赖Group Replication模式,至少3台机器 1.  准备3台 CentOS Linux 7 (Core), 修改各主机名:db-hos ...

  6. [RN] React Native 实现 类似京东 的 沉浸式状态栏和搜索栏

    React Native 实现 类似京东 的 沉浸式状态栏和搜索栏 其原理其实就是在要 隐藏 部分的那个View 前面加入 StatusBar 代码! 代码如下: <StatusBar anim ...

  7. [CQOI2007]涂色paint(BZOJ 1260)题解

    题目描述 假设你有一条长度为5的木版,初始时没有涂过任何颜色.你希望把它的5个单位长度分别涂上红.绿.蓝.绿.红色,用一个长度为5的字符串表示这个目标:RGBGR. 每次你可以把一段连续的木版涂成一个 ...

  8. yum 安装,可以list,但是无法安装Error downloading packages: 。。。。 No such file or directory

    yum 安装,可以list,但是无法安装Error downloading packages: .... No such file or directory # yum install nano Lo ...

  9. Pytorch卷积神经网络识别手写数字集

    卷积神经网络目前被广泛地用在图片识别上, 已经有层出不穷的应用, 如果你对卷积神经网络充满好奇心,这里为你带来pytorch实现cnn一些入门的教程代码 #首先导入包 import torchfrom ...

  10. js 判断一个数是否在数组中

    ,,,,,,,); ; ; i < arr.length; i++) { ){ console.log(i); flag=; break; } } ){ console.log("66 ...