Java并发之等待/通知机制

目录

  • 1 前言

    • 1.1 先来段代码放松一下
  • 2 Object wait()/notify()
    • 2.1 一段入门代码
    • 2.2 问题三连击
      • a.为什么官方说wait() 要放在while里面?
      • b.为什么wait()必须在同步方法/代码块中调用?
      • c.为什么wait(), notify() 和 notifyAll()是定义在Object里面而不是在Thread里面?
    • 2.3 wait(long timeout)
  • 3 Condition await()/signal()
    • 3.1 用Condition进行替换
    • 3.2 signal()方法后不建议添加逻辑
    • 3.3 boolean await(long time, TimeUnit unit)
  • 4 区别
  • 5 参考文档

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 参考文档

  • https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html
  • https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Condition.html
  • https://stackoverflow.com/questions/2779484/why-must-wait-always-be-in-synchronized-block

原文地址:https://www.cnblogs.com/powercto/p/10848825.html

时间: 2025-01-10 12:20:53

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

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

接着上一篇博客的 一Java线程的等待/通知模型 ,没有看过的建议先看一下.下面我们用等待通知机制来实现一个线程池 线程的任务就以打印一行文本来模拟耗时的任务.主要代码如下: 1  定义一个任务的接口. 1 /* 2 * 任务的接口 3 */ 4 public interface Task { 5 void doSomething(); 6 } 2  实现一个具体的任务. 1 /* 2 * 具体的任务 3 */ 4 public class PrintTask implements Task{

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

原子性,可见性与有序性 在多线程中,线程同步的时候一般需要考虑原子性,可见性与有序性 原子性 原子性定义:一个操作或者多个操作在执行过程中要么全部执行完成,要么全部都不执行,不存在执行一部分的情况. 以我们在Java代码中经常用到的自增操作i++为例,i++实际上并不是一步操作,而是首先对i的值加一,然后将结果再赋值给i.在单线程中不会存在问题,但如果在多线程中我们考虑这样一个情况:i是一个共享变量,初始值为0,假设线程一以执行到某一步正好进行自增操作i++,刚好对i进行了加一但是还没将值重新赋

java基础知识回顾之java Thread类学习(八)--java多线程通信等待唤醒机制经典应用(生产者消费者)

 *java多线程--等待唤醒机制:经典的体现"生产者和消费者模型 *对于此模型,应该明确以下几点: *1.生产者仅仅在仓库未满的时候生产,仓库满了则停止生产. *2.消费者仅仅在有产品的时候才能消费,仓空则等待. *3.当消费者发现仓储没有产品可消费的时候,会唤醒等待生产者生产. *4.生产者在生产出可以消费的产品的时候,应该通知等待的消费者去消费. 下面先介绍个简单的生产者消费者例子:本例只适用于两个线程,一个线程生产,一个线程负责消费. 生产一个资源,就得消费一个资源. 代码如下: pub

显式锁(四)Lock的等待通知机制Condition

?? 任意一个Java对象,都拥有一组监视器方法(定义在根类Object上),主要包括:wait( ).wait(long timeout).notify().notifyAll()方法:这些方法与关键字synchronized结合使用,可以实现 隐式锁的等待/通知机制.而显示锁Lock也实现了等待/通知机制:Condition接口也提供了类似Object的监视器方法,与Lock配合使用可以实现 显式锁的等待/通知机制,但是两者在使用方式和功能特性有所差别.总得来说,Condition接口更加灵

12.详解Condition的await和signal等待通知机制

1.Condition简介 任何一个java对象都天然继承于Object类,在线程间实现通信的往往会应用到Object的几个方法,比如wait(),wait(long timeout),wait(long timeout, int nanos)与notify(),notifyAll()几个方法实现等待/通知机制,同样的, 在java Lock体系下依然会有同样的方法实现等待/通知机制.从整体上来看Object的wait和notify/notifyAll是与对象监视器配合完成线程间的等待/通知机制

Java 中的等待唤醒机制透彻讲解

线程的状态 首先了解一下什么是线程的状态,线程状态就是当线程被创建(new),并且启动(start)后,它不是一启动就进入了执行状态(run),也不是一直都处于执行状态. 这里说一下Java 的Thread类里面有一个State方法,这个方法里面涵盖了6种线程的状态,如下: public enum State { // 尚未启动的线程的线程状态. NEW, // 可运行线程的线程状态. RUNNABLE, // 线程的线程状态被阻塞,等待监视器锁定. BLOCKED, // 等待线程的线程状态.

超强图文|并发编程【等待/通知机制】就是这个feel~

你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it well enough 现陆续将Demo代码和技术文章整理在一起 Github实践精选 ,方便大家阅读查看,本文同样收录在此,觉得不错,还请Star 并发编程为什么会有等待通知机制 上一篇文章说明了 Java并发死锁解决思路 , 解决死锁的思路之一就是 破坏请求和保持条件, 所有柜员都要通过唯一的账本管理员一次性拿到所有

java基础知识回顾之java Thread类学习(七)--java多线程通信等待唤醒机制(wait和notify,notifyAll)

1.wait和notify,notifyAll: wait和notify,notifyAll是Object类方法,因为等待和唤醒必须是同一个锁,不可以对不同锁中的线程进行唤醒,而锁可以是任意对象,所以可以被任意对象调用的方法,定义在Object基类中. wait()方法:对此对象调用wait方法导致本线程放弃对象锁,让线程处于冻结状态,进入等待线程的线程池当中.wait是指已经进入同步锁的线程,让自己暂时让出同步锁,以便使其他正在等待此锁的线程可以进入同步锁并运行,只有其它线程调用notify方

java多线程的等待唤醒机制及如何解决同步过程中的安全问题

/* class Person{ String name; String sex; boolean flag = true; public void setPerson(String name, String sex){ this.sex=sex; this.name=name; } } class Input implements Runnable{ int x=0; Person p; Input(Person p){ this.p=p; } public void run(){ while