首页 > java多线中的条件对象await()问题

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

if(accounts[from]<amount){
改成while

sufficientFunds.signalAll(); //唤醒条件对象等待队列中的所有等待的线程
改成
signal();


问题解决了,原因是signalAll()唤醒等待的线程后,线程不会去重新判断余额,而是接着往下运行,所以就出现了账户余额负的情况。

修改Bank.java 的transfer方法如下:

public void transfer(int from,int to , double amount) throws InterruptedException{

    try{
        bankLock.lock(); //获取锁
        //加入循环判断
        while(true){
            if(accounts[from]<amount){
                //return;
                sufficientFunds.await(); //保证账户金额不会为负
            }else{
                break;
            }
        }
        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();//释放锁
    }

}
【热门文章】
【热门文章】