
本文深入探讨了在java中实现多线程共享账户同步的机制,重点讲解如何利用`synchronized`关键字确保并发操作的原子性,并通过`wait()`和`notifyall()`方法有效协调线程间的存取款活动,以维护账户余额的最小和最大限制,从而避免数据不一致和死锁等并发问题。
在多线程编程中,当多个线程尝试访问和修改同一个共享资源(例如本例中的银行账户)时,如果不加以适当的同步控制,就可能导致数据不一致、竞态条件等问题。Java提供了强大的并发工具来解决这些问题,其中最基础且常用的就是synchronized关键字以及Object类的wait()、notify()和notifyAll()方法。
synchronized关键字用于确保同一时间只有一个线程可以执行特定的代码块或方法,从而保护共享资源。而wait()、notify()和notifyAll()则用于线程间的协作,允许线程在特定条件不满足时暂停执行(等待),并在条件满足时被其他线程唤醒。
我们将通过一个模拟银行账户存取款的场景来演示这些概念。设有一个银行账户,由两个人(两个线程)共享,他们可以同时进行存款和取款操作。账户有最大余额(500欧元)和最小余额(1欧元)限制。当存款操作会导致余额超过上限,或取款操作会导致余额低于下限时,当前线程需要等待,直到条件允许。
系统包含三个核心类:
立即学习“Java免费学习笔记(深入)”;
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;
}
}关键点说明:
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; // 退出循环
}
}
}
}关键点说明:
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("主线程被中断。");
}
}
}关键点说明:
通过本教程,我们学习了如何在Java中利用synchronized关键字、wait()和notifyAll()方法来有效地管理多线程对共享资源的并发访问。这种模式在处理诸如生产者-消费者问题、线程池等需要线程协作的场景中非常常见。理解这些并发原语的工作机制对于编写健壮、高效的Java多线程
以上就是Java多线程账户同步:使用wait()和notifyAll()管理共享资源的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号