wait,notify,notifyAll原理以及实际使用场景

简介: wait,notify,notifyAll原理以及实际使用场景

wait : 使当前获取锁的线程挂起。可以选择wait(time,unit) 设置时间和时间单位。

notify : 使当前获取过锁并被wait过的线程由阻塞状态转为就绪状态(随机唤醒)。

notifyAll : 使当前获取过锁并被wait过的所有线程由阻塞状态转为就绪状态。

假设现在系统中有两个线程,这两个线程分别代表存款者和取钱者——现在假设系统有一种特殊的要求,系统要求存款者和取钱者不断地重复存款、取钱的动作,而且要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱。不允许存款者连续两次存钱,也不允许取钱者连续两次取钱。

怎么实现呢? 其实可以使用wait,notify,notifyAll 来实现,notifyAll的话可以唤醒所有挂起的线程,取钱者的话我生成了多个例子,因此取钱者就用这个了。notify的话只能随机唤醒一个wait的线程,如果存钱者只有一个的话可以使用notify。但是要注意,一定要对wait过的线程才能使用notify或者notifyAll,否则没有任何意义。

看代码:

账户类

package com.zp.me.thread.wait;
 
import lombok.Data;
 
/**
 * FileName: Account
 * Author:  zp
 * Date:    2022/2022/8/30/20:40
 * Description:
 */
@Data
public class Account {
    private String accountNo;
    private double balance;
 
    public Account() {
    }
 
    public Account(String accountNo, double balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }
 
    // 如果为true 则代表有存款,取款者可取款,反之存钱者可存钱
    private boolean flag = false;
 
 
    /***
     * @Description: 取钱
     * @Param: [money]
     * @return: void
     * @Author: zp
     * @Date: 2022/8/30
     */
    public synchronized void draw(double money) {
        try {
            if (flag) {
                balance = balance - money;
                System.out.println(Thread.currentThread().getName() + "取了" + money + "元");
                flag = false;
                notifyAll();
            } else {
 
                wait();
 
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
    /***
     * @Description: 存钱
     * @Param: [money]
     * @return: void
     * @Author: zp
     * @Date: 2022/8/30
     */
    public synchronized void deposit(double money) {
        try {
            if (flag) {
 
                wait();
 
            } else {
                balance = balance + money;
                System.out.println(Thread.currentThread().getName() + "存了" + money + "元");
                flag = true;
                notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
 
}

取钱线程

package com.zp.me.thread.wait;
 
import lombok.Data;
 
/**
 * FileName: DrawThread
 * Author:  zp
 * Date:    2022/2022/8/30/20:54
 * Description:
 */
@Data
public class DrawThread extends Thread{
    private Account account;
    private double money;
 
    public DrawThread(String name,Account account, double money) {
        super(name);
        this.account = account;
        this.money = money;
    }
 
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            account.draw(money);
        }
    }
}

存钱线程

package com.zp.me.thread.wait;
 
import lombok.Data;
 
/**
 * FileName: DrawThread
 * Author:  zp
 * Date:    2022/2022/8/30/20:54
 * Description:
 */
@Data
public class DepositThread extends Thread{
    private Account account;
    private double money;
 
    public DepositThread(String name, Account account, double money) {
        super(name);
        this.account = account;
        this.money = money;
    }
 
    @Override
    public void run() {
        for (int i = 0; i < 1; i++) {
            account.deposit(money);
        }
    }
}

测试类

package com.zp.me.thread.wait;
 
/**
 * FileName: ThreadTest
 * Author:  zp
 * Date:    2022/2022/8/30/21:46
 * Description:
 */
public class ThreadTest {
    public static void main(String[] args) {
        Account account = new Account("0000001", 0.00);
        new DepositThread("存钱者1",account,500).start();
        new DrawThread("取钱者1",account,500).start();
        new DrawThread("取钱者2",account,500).start();
        new DrawThread("取钱者3",account,500).start();
 
    }
}

结果:

Connected to the target VM, address: '127.0.0.1:61034', transport: 'socket'
存钱者1存了500.0元
取钱者3取了500.0元

可以看到,不会出现存钱者多存或者取钱者多取的情况。

目录
相关文章
|
Ubuntu
ubuntu挂载硬盘
ubuntu挂载硬盘
700 0
|
存储 算法 关系型数据库
InnoDb行格式、数据页结构、索引底层原理和如何建立索引
InnoDb行格式、数据页结构、索引底层原理和如何建立索引
353 0
|
Cloud Native 关系型数据库 数据库
云原生之使用Docker部署Mariadb数据库
云原生之使用Docker部署Mariadb数据库
959 1
云原生之使用Docker部署Mariadb数据库
|
消息中间件 Prometheus 监控
使用jmx exporter采集kafka指标
使用jmx exporter采集kafka指标
622 4
|
机器学习/深度学习 人工智能 搜索推荐
谈谈内容创作中的UGC、PGC、AIGC,在创意设计领域的应用与进化
在数字化时代,内容创作经历了从UGC(用户生成内容)到PGC(专业生产内容),再到AIGC(人工智能生成内容)的转变。UGC打破了传统边界,让每个人都能参与创作,但质量参差不齐;PGC代表专业水准,提供高质量内容,但成本高且制作周期长;AIGC借助AI技术大幅提升效率,实现个性化定制,但面临版权、伦理和情感表达的挑战。Adobe国际认证等专业资格成为了衡量设计师能力的新标准,帮助设计师适应这一变革并引领创新。三种模式相互补充,共同推动创意设计领域不断发展。
会员管理系统实战开发教程06-会员充值
会员管理系统实战开发教程06-会员充值
|
关系型数据库 网络安全 数据安全/隐私保护
你会开启Postgresql 的SSL单向认证 配置?
你会开启Postgresql 的SSL单向认证 配置?
980 0
你会开启Postgresql 的SSL单向认证 配置?
|
数据库 iOS开发 MacOS
Lyricsx让歌词悬浮于最顶层
Lyricsx让歌词悬浮于最顶层
865 0
|
存储 消息中间件 缓存
RocketMQ如何保证消息的可靠性?
消息的发送方式有哪几种?存储消息的可靠性面临哪些挑战?消费消息的确认机制是怎样的?本文通过分析消息流转的整个过程,从消息发送、消息存储和消息消费三个阶段介绍RocketMQ是如何保证消息的可靠性的。
RocketMQ如何保证消息的可靠性?
|
算法 Java 程序员
5千字详细讲解java并发编程的AQS
本文讲解AQS的组成,实现原理,应用,源码解析
5千字详细讲解java并发编程的AQS