0

0

Java多线程账户同步:使用wait()和notifyAll()管理共享资源

DDD

DDD

发布时间:2025-11-25 20:50:15

|

892人浏览过

|

来源于php中文网

原创

Java多线程账户同步:使用wait()和notifyAll()管理共享资源

本文深入探讨了在java中实现多线程共享账户同步的机制,重点讲解如何利用`synchronized`关键字确保并发操作的原子性,并通过`wait()`和`notifyall()`方法有效协调线程间的存取款活动,以维护账户余额的最小和最大限制,从而避免数据不一致和死锁等并发问题。

理解Java多线程与共享资源同步

在多线程编程中,当多个线程尝试访问和修改同一个共享资源(例如本例中的银行账户)时,如果不加以适当的同步控制,就可能导致数据不一致、竞态条件等问题。Java提供了强大的并发工具来解决这些问题,其中最基础且常用的就是synchronized关键字以及Object类的wait()、notify()和notifyAll()方法。

synchronized关键字用于确保同一时间只有一个线程可以执行特定的代码块或方法,从而保护共享资源。而wait()、notify()和notifyAll()则用于线程间的协作,允许线程在特定条件不满足时暂停执行(等待),并在条件满足时被其他线程唤醒。

案例分析:共享银行账户系统

我们将通过一个模拟银行账户存取款的场景来演示这些概念。设有一个银行账户,由两个人(两个线程)共享,他们可以同时进行存款和取款操作。账户有最大余额(500欧元)和最小余额(1欧元)限制。当存款操作会导致余额超过上限,或取款操作会导致余额低于下限时,当前线程需要等待,直到条件允许。

系统包含三个核心类:

立即学习Java免费学习笔记(深入)”;

  • BPA:主程序类,负责创建账户和人物线程。
  • Persona:代表操作账户的人,每个Persona对象是一个独立的线程,执行存取款操作。
  • Cuenta:代表银行账户,是共享资源,包含余额及存取款的同步逻辑。

实现细节与代码解析

Cuenta 类:账户的同步逻辑

Cuenta类是实现同步机制的关键。它的ingreso(存款)和retiro(取款)方法都必须是synchronized的,以确保对账户余额的操作是原子性的。同时,在这两个方法内部,我们利用wait()和notifyAll()来处理余额限制。

package BPA;

import java.util.Random;

public class Cuenta {

    private int saldo; // 当前余额
    private final int saldoMax; // 最大余额
    private final int saldoMin = 1; // 最小余额

    public Cuenta(int saldoInicial, int saldoMaximo) {
        this.saldo = saldoInicial;
        this.saldoMax = saldoMaximo;
    }

    /**
     * 取款操作
     * @param nombre 操作人名称
     */
    public synchronized void retiro(String nombre) {
        int dinero = new Random().nextInt(350) + 1; // 随机生成取款金额 (1-350)

        // 使用while循环判断条件,防止虚假唤醒和条件再次不满足
        while ((saldo - dinero) < saldoMin) {
            System.out.println("  ****  账户余额不足!" + nombre + " 尝试取出: " + dinero + " 现有余额: " + getSaldo() + " ****");
            System.out.println(" ---- " + nombre + " 正在等待存款以进行取款 ---- ");
            try {
                wait(); // 余额不足,线程等待,并释放Cuenta对象的锁
            } catch (InterruptedException e) {
                System.out.println(nombre + " 的取款等待被中断。");
                Thread.currentThread().interrupt(); // 重新设置中断标志
                return; // 退出方法
            }
        }
        // 条件满足,执行取款
        this.saldo -= dinero;
        System.out.println("Name: " + nombre + " extract cash: " + dinero + " TOTAL CASH: " + getSaldo());
        notifyAll(); // 唤醒所有等待在Cuenta对象上的线程,包括可能等待存款的线程
    }

    /**
     * 存款操作
     * @param nombre 操作人名称
     */
    public synchronized void ingreso(String nombre) {
        int dinero = new Random().nextInt(350) + 1; // 随机生成存款金额 (1-350)

        // 使用while循环判断条件
        while ((saldo + dinero) > saldoMax) {
            System.out.println(" **** 账户已达上限!" + nombre + " 尝试存入: " + dinero + " 现有余额: " + getSaldo() + " ****");
            System.out.println(" ---- " + nombre + " 正在等待取款以进行存款 ---- ");
            try {
                wait(); // 余额超上限,线程等待,并释放Cuenta对象的锁
            } catch (InterruptedException e) {
                System.out.println(nombre + " 的存款等待被中断。");
                Thread.currentThread().interrupt(); // 重新设置中断标志
                return; // 退出方法
            }
        }
        // 条件满足,执行存款
        this.saldo += dinero;
        System.out.println("Name: " + nombre + " deposit cash: " + dinero + " TOTAL CASH: " + getSaldo());
        notifyAll(); // 唤醒所有等待在Cuenta对象上的线程,包括可能等待取款的线程
    }

    public int getSaldo() {
        return saldo;
    }
}

关键点说明:

Descript
Descript

一个多功能的音频和视频编辑引擎

下载
  • synchronized 方法: ingreso和retiro方法被synchronized修饰,这意味着任何时候只有一个线程能够进入这些方法,从而保证了对saldo变量的原子性操作。锁定的对象是Cuenta实例本身。
  • wait(): 当存取款条件不满足时(例如,取款会导致余额低于saldoMin),线程会调用wait()。这会使当前线程进入等待状态,并释放它持有的Cuenta对象的锁。这样,其他线程就有机会获取锁并执行存取款操作,从而改变账户状态。
  • notifyAll(): 在每次成功存入或取出金额后,调用notifyAll()。这会唤醒所有正在等待Cuenta对象锁的线程。这些被唤醒的线程会尝试重新获取锁,并在获取锁后从wait()的地方继续执行。
  • while 循环条件判断: wait()方法被唤醒后,线程会重新检查条件是否满足(while ((saldo - dinero)

Persona 类:操作账户的线程

Persona类继承自Thread,每个实例代表一个独立的线程,负责循环调用Cuenta对象的存取款方法。

package BPA;

import java.util.Random;

public class Persona extends Thread {

    String nombre;
    private Cuenta cuenta;

    public Persona(String nombre, Cuenta cuenta) {
        this.nombre = nombre;
        this.cuenta = cuenta;
    }

    @Override
    public void run() {
        while (true) {
            // 线程循环进行存取款操作
            cuenta.ingreso(nombre);
            cuenta.retiro(nombre);

            try {
                // 模拟操作间隔,避免CPU空转过快,同时给其他线程机会
                Thread.sleep(new Random().nextInt(500) + 500); // 随机暂停0.5到1.0秒
            } catch (InterruptedException e) {
                System.out.println(nombre + " 的操作被中断。");
                Thread.currentThread().interrupt(); // 重新设置中断标志
                break; // 退出循环
            }
        }
    }
}

关键点说明:

  • Persona线程直接调用Cuenta对象的ingreso和retiro方法。由于这些方法本身已经包含了同步逻辑,Persona类的run()方法不需要额外的synchronized块或wait()/notifyAll()调用来管理账户同步。
  • Thread.sleep():在每次存取款操作后,线程会暂停一段时间。这有助于模拟真实世界的延迟,并让其他线程有机会运行,避免某个线程过度占用CPU。

BPA 类:主程序入口

BPA类是程序的入口点,负责创建Cuenta对象和Persona线程,并启动它们。

package BPA;

public class BPA {
    public static void main(String[] args) {
        // 创建一个初始余额为40,最大余额为500的账户
        Cuenta laCuenta = new Cuenta(40, 500);

        // 创建两个操作人线程,并关联到同一个账户
        Persona Ramon = new Persona("Ramon", laCuenta);
        Persona Quique = new Persona("Quique", laCuenta);

        // 启动线程
        Quique.start();
        Ramon.start();

        try {
            // 等待两个线程执行完毕(尽管在本例中它们会无限循环,除非被中断)
            Quique.join();
            Ramon.join();
        } catch (InterruptedException ex) {
            System.out.println("主线程被中断。");
        }
    }
}

关键点说明:

  • join()方法:main线程调用Quique.join()和Ramon.join()会使main线程等待这两个Persona线程执行完毕。在我们的例子中,Persona线程是无限循环的,所以main线程会一直等待,直到程序被手动终止或线程被中断。

正确实现的关键点与注意事项

  1. wait()和notifyAll()的调用位置: 它们必须在synchronized块或synchronized方法内部调用,并且必须在当前线程持有该对象监视器(锁)的情况下调用。否则,会抛出IllegalMonitorStateException。
  2. wait()释放锁: wait()方法会释放当前线程持有的对象锁,并进入等待状态。这是实现线程协作的关键,允许其他线程有机会获取锁并修改共享资源。
  3. notify() vs notifyAll():
    • notify():随机唤醒一个等待在该对象上的线程。
    • notifyAll():唤醒所有等待在该对象上的线程。在多生产者-多消费者或多存多取场景中,通常推荐使用notifyAll(),以确保所有相关线程都有机会检查条件并继续执行,避免潜在的死锁或效率问题。
  4. 条件判断使用while循环: 永远不要用if语句来检查wait()后的条件。线程可能被虚假唤醒,或者在被唤醒时,由于其他线程的操作,条件再次不满足。while循环能确保线程只有在条件真正满足时才继续执行。
  5. 处理InterruptedException: wait()和sleep()方法都可能抛出InterruptedException。在捕获此异常时,最佳实践是重新设置线程的中断状态(Thread.currentThread().interrupt()),以便调用上层的代码能够感知到中断,并根据需要进行处理。

总结

通过本教程,我们学习了如何在Java中利用synchronized关键字、wait()和notifyAll()方法来有效地管理多线程对共享资源的并发访问。这种模式在处理诸如生产者-消费者问题、线程池等需要线程协作的场景中非常常见。理解这些并发原语的工作机制对于编写健壮、高效的Java多线程

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

837

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

741

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

736

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

399

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

446

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

430

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16926

2023.08.03

PHP WebSocket 实时通信开发
PHP WebSocket 实时通信开发

本专题系统讲解 PHP 在实时通信与长连接场景中的应用实践,涵盖 WebSocket 协议原理、服务端连接管理、消息推送机制、心跳检测、断线重连以及与前端的实时交互实现。通过聊天系统、实时通知等案例,帮助开发者掌握 使用 PHP 构建实时通信与推送服务的完整开发流程,适用于即时消息与高互动性应用场景。

8

2026.01.19

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.7万人学习

C# 教程
C# 教程

共94课时 | 7万人学习

Java 教程
Java 教程

共578课时 | 47.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号