简体   繁体   中英

Locks and synchronized in java

i am currently working with locks in java and synchronizing methods. I have an example program with three classes: an account, with two methods for withdrawing and depositing money and a shared variable balance; and the Spender and Saver classes.

class Account{

    private float balance = 0;
    public void depositMoney(float amount){
    float tmp;
    synchronized(this){
        tmp = balance;
        System.out.println("(Adding money): the initial balance is: "
                   + tmp);
        tmp +=amount;
        balance = tmp;
        System.out.println("(Adding money): the final balance is: "
                   + balance);
        this.notify();
    }
    }
    
    public void withdrawMoney(float amount){
    float tmp;
    
    synchronized(this){

        while (balance <= 0){
        try {
            this.wait();
        } catch (Exception e) {}
        }
        
        tmp =  balance;
        System.out.println("(Withdrawing money): the initial balance is: "
                   + tmp);
        tmp -=amount;
        balance = tmp;
        System.out.println("(Withdrawing money): the final balance is: "
                   + balance);
        
    }
    }

}
class Saver extends Thread{

    private Account account;
    
    Scrooge(Account account){
    this.account = account;
    }
    
    public void run(){
    for (int i=0; i < 2; i++){
        account.depositMoney(1200);
    }
    }
}
class Spender extends Thread{

    private Account account;
    
    Donald(Account account){
    this.account = account;
    }
    
    public void run(){
    for (int i=0; i< 2; i++){
        account.withdrawMoney(400);
    }
    }
}
public class Bankv4{

    public static void main(String[] args){
    Account account = new Account();
    Spender s1 = new Spender(account);
    Spender s2 = new Spender(account);
    Spender s3 = new Spender(account);
    Saver saver = new Saver(account);
    s1.start();
    s2.start();
    s3.start();
    saver.start();
    }
}

The problem is that this code does not always work. It works in the case that the Saver class runs the depositMoney method 30 times with a quantity of 100 and the Spender class runs the withdrawing money 10 times with a quantity of 100 (as there are 3 Spender Threads 10 * 3 * 100 = 3000, the amount that the Saver class deposit in the account).

In the code above, the problem is that although the Saver class is depositing the same quantity as the Spender is withdrawing, it does it in less iterations, which causes a deadlock as the Spender threads run wait() but the Saver class does not run notify() as its thread has ended, causing the program not to finish.

Can anyone solve this? Thanks in advance

When the saver deposits money, notify() only releases one waiting thread. The spender that wakes up will not spend all of the money, but nothing wakes up a new spender to spend the rest. After the last deposit, the remaining spenders will wait forever.

You have 2 choices to fix this.

The safe way is to always use notifyAll() instead of notify. That way all the spenders will wake up, grab the lock, and try to spend again.

The more performant way is the have the withdraw() method call notify() after removing funds. This way only one spender will wake up, but it's dangerous technique, because you have to be certain that every thread that might be waiting will certainly wake up another thread if necessary.

Four things.

  1. Use ExecutorService to launch several threads.
  2. Call notifyAll() rather than notify() .
  3. Catch InterruptedException rather than Exception and at least print something out so that you know that it occurred.
  4. Since I am using ExecutorService , classes Saver and Spender do not need to extend Thread , they just need to implement Runnable .

I changed only classes Account and Bankv4 — even though classes Saver and Spender , according to the code in your question, do not compile. (I leave that problem to the OP :-)

public class Account {
    private float balance = 0;

    public void depositMoney(float amount) {
        float tmp;
        synchronized (this) {
            tmp = balance;
            System.out.println("(Adding money): the initial balance is: " + tmp);
            tmp += amount;
            balance = tmp;
            System.out.println("(Adding money): the final balance is: " + balance);
            this.notifyAll(); // CHANGED THIS
        }
    }

    public void withdrawMoney(float amount) {
        float tmp;
        synchronized (this) {
            while (balance <= 0) {
                try {
                    this.wait();
                }
                catch (InterruptedException e) {
                    System.err.println("'Account.withdrawMoney()' interrupted."); // ADDED THIS
                }
            }
            tmp = balance;
            System.out.println("(Withdrawing money): the initial balance is: " + tmp);
            tmp -= amount;
            balance = tmp;
            System.out.println("(Withdrawing money): the final balance is: " + balance);

        }
    }
}

public class Bankv4 {

    public static void main(String[] args) {
        Account account = new Account();
        Spender s1 = new Spender(account);
        Spender s2 = new Spender(account);
        Spender s3 = new Spender(account);
        Saver saver = new Saver(account);
        ExecutorService es = Executors.newFixedThreadPool(4);
        es.execute(s1);
        es.execute(s2);
        es.execute(s3);
        es.execute(saver);
        es.shutdown();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM