java多线中的条件对象await()问题
最近在研究java多线程,在研究到条件对象的时候,问题出现了。本来该阻塞的线程,没有阻塞。
情景如下:
有一个银行类Bank,Bank有一个方法用来从一个账户转账到另外一个账户。实现逻辑如下:
try{
bankLock.lock(); //获取锁
if(accounts[from]<amount){
//return;
sufficientFunds.await(); //保证账户金额不会为负
}
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d,the remainder is %10.2f ",amount,from,to,accounts[from]);
accounts[to] += amount;
System.out.printf(" Total Balance : %10.2f%n",getTotalBalance());
sufficientFunds.signalAll(); //唤醒条件对象等待队列中的所有等待的线程
}finally{
bankLock.unlock();//释放锁
}
在每次转账前,获取锁,保证操作的原子性,然后判断账户金额是否足够转账金额,如果不够,通过条件对象等待,并释放同步锁,以保证其他线程可以获取该锁,并给该账户充值,如果够,则进行转账,转账结束后唤醒条件对象上等待的所有线程,所有操作都结束后释放锁。
同时有一个线程对象一直调用Bank对象的转账方法,进行转账。实现逻辑如下:
try{
while(true){
//获取一个随机账户
int toAccount = (int)(bank.size()*Math.random());
//获取随机转账金额
double amount = maxAmount * Math.random();
//调用转账方法
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int)(DELAY*Math.random()));
}
}catch(InterruptedException e){
}
最后是一个启动线程测试类:
Bank b = new Bank(NACCOUNTS,INITIAL_BALANCE);
int i ;
for(i = 0 ;i < NACCOUNTS;i++){
BankRunnable r = new BankRunnable(b, i, INITIAL_BALANCE);
Thread t = new Thread(r);
t.start();
}
这个类就是循环创建与银行账户相同个数的线程,一个线程负责一个账户金额的转出,转入到一个随机的账户中。
执行上面的代码,理论上每个账户的余额都不可能为负,因为在每次转账的时候就会进行账户余额与转账金额的比较,如果不够,就是通过条件对象调用await()使当前线程阻塞,直到其他线程调用了signalAll()唤醒该条件对象上等待的线程,但是signalAll()也并不是让这些等待的线程立即激活,而是仅仅解除了等待线程的阻塞,与其他线程重新竞争同步锁。所以当线程重新获得同步锁后,他依然会继续进行余额校验。。。这样的话账户余额是不可能为负值的。
但是问题是当循环进行转账是有些账户却莫名其妙的出现了负值,有点不解,希望大神指教。
下面是测试代码的完整版本:
Bank.java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Bank {
private final double[] accounts;
//锁
private Lock bankLock ;
private Condition sufficientFunds ;
public Bank(int n ,double initialBalance){
accounts = new double[n];
for(int i = 0 ;i <n ;i ++){
accounts[i] = initialBalance;
}
bankLock = new ReentrantLock();
sufficientFunds = bankLock.newCondition();
}
public void transfer(int from,int to , double amount) throws InterruptedException{
try{
bankLock.lock(); //获取锁
if(accounts[from]<amount){
//return;
sufficientFunds.await(); //保证账户金额不会为负
}
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d,the remainder is %10.2f ",amount,from,to,accounts[from]);
accounts[to] += amount;
System.out.printf(" Total Balance : %10.2f%n",getTotalBalance());
sufficientFunds.signalAll(); //唤醒条件对象等待队列中的所有等待的线程
}finally{
bankLock.unlock();//释放锁
}
}
public double getTotalBalance() throws InterruptedException{
bankLock.lock();
try{
double sum = 0;
for(double d : accounts){
sum += d;
}
return sum;
}finally{
bankLock.unlock();
}
}
public int size(){
return accounts.length;
}
}
BankRunnable.java
public class BankRunnable implements Runnable {
private Bank bank;
private int fromAccount;
private double maxAmount;
private final int DELAY = 10;
public BankRunnable(Bank bank,int from,double amount){
this.bank = bank;
this.fromAccount = from ;
this.maxAmount = amount;
}
@Override
public void run() {
try{
while(true){
int toAccount = (int)(bank.size()*Math.random());
double amount = maxAmount * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int)(DELAY*Math.random()));
}
}catch(InterruptedException e){
}
}
}
BankRunnableTest.java
public class BankRunnableTest {
private static final int NACCOUNTS = 100;
private static final double INITIAL_BALANCE = 1000;
public static void main(String[] args) {
Bank b = new Bank(NACCOUNTS,INITIAL_BALANCE);
int i ;
for(i = 0 ;i < NACCOUNTS;i++){
BankRunnable r = new BankRunnable(b, i, INITIAL_BALANCE);
Thread t = new Thread(r);
t.start();
}
}
}
执行结果部分:
Thread[Thread-5,5,main] 270.14 from 5 to 30,the remainder is 9373.35 Total Balance : 100000.00
Thread[Thread-1,5,main] 784.33 from 1 to 84,the remainder is 661.14 Total Balance : 100000.00
Thread[Thread-45,5,main] 11.12 from 45 to 65,the remainder is 9655.22 Total Balance : 100000.00
Thread[Thread-1,5,main] 230.77 from 1 to 10,the remainder is 430.37 Total Balance : 100000.00
Thread[Thread-31,5,main] 101.21 from 31 to 22,the remainder is 4781.37 Total Balance : 100000.00
Thread[Thread-88,5,main] 28.85 from 88 to 47,the remainder is -1185.99 Total Balance : 100000.00
Thread[Thread-11,5,main] 259.82 from 11 to 68,the remainder is -4233.61 Total Balance : 100000.00
Thread[Thread-71,5,main] 704.97 from 71 to 10,the remainder is 6308.89 Total Balance : 100000.00
Thread[Thread-15,5,main] 116.78 from 15 to 80,the remainder is -5700.42 Total Balance : 100000.00
Thread[Thread-27,5,main] 253.64 from 27 to 5,the remainder is -3566.59 Total Balance : 100000.00
Thread[Thread-67,5,main] 384.56 from 67 to 67,the remainder is -4888.38 Total Balance : 100000.00
Thread[Thread-33,5,main] 925.58 from 33 to 46,the remainder is -2872.45 Total Balance : 100000.00
Thread[Thread-76,5,main] 882.94 from 76 to 61,the remainder is -10867.94 Total Balance : 100000.00
Thread[Thread-41,5,main] 963.41 from 41 to 35,the remainder is -38.63 Total Balance : 100000.00
Thread[Thread-52,5,main] 926.76 from 52 to 52,the remainder is -298.91 Total Balance : 100000.00
Thread[Thread-42,5,main] 606.69 from 42 to 17,the remainder is 734.35 Total Balance : 100000.00
Thread[Thread-22,5,main] 984.00 from 22 to 64,the remainder is 799.38 Total Balance : 100000.00
Thread[Thread-59,5,main] 165.41 from 59 to 6,the remainder is 2505.97 Total Balance : 100000.00
Thread[Thread-38,5,main] 886.59 from 38 to 9,the remainder is 11343.17 Total Balance : 100000.00
Thread[Thread-1,5,main] 872.49 from 1 to 20,the remainder is -442.12 Total Balance : 100000.00
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
问题解决了,原因是signalAll()唤醒等待的线程后,线程不会去重新判断余额,而是接着往下运行,所以就出现了账户余额负的情况。
修改Bank.java 的transfer方法如下:
if(accounts[from]<amount){
改成while
sufficientFunds.signalAll(); //唤醒条件对象等待队列中的所有等待的线程
改成
signal();