Locks and synchronized in Java.

huangapple go评论62阅读模式
英文:

Locks and synchronized in java

问题

我目前正在使用Java中的锁和同步方法。我有一个带有三个类的示例程序:一个帐户,带有两个提取和存款方法以及一个共享变量余额;还有Spender和Saver类。

class Account{

    private float balance = 0;
    public synchronized void depositMoney(float amount){
        float tmp;
        tmp = balance;
        System.out.println("(添加金额):初始余额为:" + tmp);
        tmp += amount;
        balance = tmp;
        System.out.println("(添加金额):最终余额为:" + balance);
        this.notify();
    }
    
    public synchronized void withdrawMoney(float amount){
        float tmp;
    
        while (balance <= 0){
            try {
                this.wait();
            } catch (Exception e) {}
        }
        
        tmp = balance;
        System.out.println("(取款):初始余额为:" + tmp);
        tmp -= amount;
        balance = tmp;
        System.out.println("(取款):最终余额为:" + balance);
    }

}

class Saver extends Thread{

    private Account account;
    
    public Saver(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;
    
    public Spender(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();
    }
}

问题是这段代码并不总是有效。它在Saver类运行depositMoney方法30次,每次存入100的情况下有效,而Spender类运行取款方法10次,每次取款100(由于有3个Spender线程,10 * 3 * 100 = 3000,这是Saver类在帐户中存入的金额)。

在上面的代码中,问题在于虽然Saver类存入的金额与Spender类取出的金额相同,但它的迭代次数较少,这导致了死锁,因为Spender线程运行wait(),但Saver类不会运行notify(),因为它的线程已经结束,导致程序无法完成。

有人能解决这个问题吗?提前致谢。

英文:

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(&quot;(Adding money): the initial balance is: &quot;
+ tmp);
tmp +=amount;
balance = tmp;
System.out.println(&quot;(Adding money): the final balance is: &quot;
+ balance);
this.notify();
}
}
public void withdrawMoney(float amount){
float tmp;
synchronized(this){
while (balance &lt;= 0){
try {
this.wait();
} catch (Exception e) {}
}
tmp =  balance;
System.out.println(&quot;(Withdrawing money): the initial balance is: &quot;
+ tmp);
tmp -=amount;
balance = tmp;
System.out.println(&quot;(Withdrawing money): the final balance is: &quot;
+ balance);
}
}
}

<!-- -->

class Saver extends Thread{
private Account account;
Scrooge(Account account){
this.account = account;
}
public void run(){
for (int i=0; i &lt; 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&lt; 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

答案1

得分: 3

存款人存款时,notify() 仅释放一个等待的线程。唤醒的消费者不会将所有资金都花费,但没有任何机制唤醒新的消费者来花费剩余的资金。在最后一笔存款之后,剩余的消费者将永远等待。

你有两种选择来解决这个问题。

安全的方法是始终使用 notifyAll() 而不是 notify。这样所有的消费者都会被唤醒,获取锁,并尝试再次消费。

性能更高的方法是在 withdraw() 方法在移除资金后调用 notify()。这样只会唤醒一个消费者,但这是一种危险的技术,因为你必须确保每个可能在等待的线程都会在必要时唤醒另一个线程。

英文:

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.

答案2

得分: 0

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();
    }
}
英文:

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 &mdash; even though classes Saver and Spender, according to the code in your question, do not compile. (I leave that problem to the OP Locks and synchronized in Java.

public class Account {
    private float balance = 0;

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

    public void withdrawMoney(float amount) {
        float tmp;
        synchronized (this) {
            while (balance &lt;= 0) {
                try {
                    this.wait();
                }
                catch (InterruptedException e) {
                    System.err.println(&quot;&#39;Account.withdrawMoney()&#39; interrupted.&quot;); // ADDED THIS
                }
            }
            tmp = balance;
            System.out.println(&quot;(Withdrawing money): the initial balance is: &quot; + tmp);
            tmp -= amount;
            balance = tmp;
            System.out.println(&quot;(Withdrawing money): the final balance is: &quot; + 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();
    }
}

huangapple
  • 本文由 发表于 2020年10月3日 23:39:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/64185985.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定