java - 为什么要使用条件,“阻塞”和“等待”之间有什么区别

标签 java multithreading locking conditional-statements

“Core Java”一书中有一个示例,它将资金从一个帐户转移到另一个帐户。我不知道条件的用处是什么?在书中,它告诉我们:

if we just lock and wait without condition, it gets a deadlock:


private final double[] accounts;
private Lock bankLock;
private Condition sufficientFunds;

public void transfer(int from, int to, int amount) {
    bankLock.lock();
    try { 
        while (accounts[from] < amount) {
            // wait...
        }
        // transfer funds . . . 
    } finally { 
        bankLock.unlock(); 
    }
}

Now, what do we do when there is not enough money in the account? We wait until some other thread has added funds. But this thread has just gained exclusive access to the bankLock, so no other thread has a chance to make a deposit.

When call


sufficientFunds.await();

the current thread is now deactivated and gives up the lock. This lets in another thread that can, we hope, increase the account balance.


Lock锁定代码,而条件放弃了锁定,但是我不知道什么是条件,为什么在钱不够的情况下为什么不简单地解锁块呢?
线程状态“阻塞”和“等待”之间有什么区别?
阻塞:线程无法运行;
等待中:线程也无法运行。
有什么不同吗?

另一个问题:
while (accounts[from] < amount) {
   ...
   sufficientFunds.await();

为什么不写if
if (accounts[from] < amount) {
    ...

最佳答案

Bank类有一个特殊要求:如果要求将一定数量的钱从一个帐户转移到另一个帐户,并且源帐户上没有足够的资金,则它必须等到存入足够的钱才能进行转移。您可以运行一个循环,以检查每次迭代是否有足够的资金,并仅在满足以下条件时获取锁:

while (true) {
    if (accounts[from] >= amount) {
        bankLock.lock();
        try {
            if (account[from] >= amount) {
                // do the transfer here...
                break;
            }
        } finally {
            bankLock.unlock();
        }
    }
}

但是这种方法:
  • 浪费CPU资源进行持续检查(无人检查)
    在几小时或几天内存足够的钱?)
  • 看起来笨重而不是惯用语
  • 并不总是有效(原因不在此问题的范围内,如果您对此感兴趣,可以在注释中提供指向解释的链接)

  • 因此,您需要某种机制来告诉您,您只是在等待帐户中的某些更改。如果没有人存入钱,一次又一次地检查一个账户中的钱是浪费的。不仅如此,您还需要在有人存入资金后立即获取锁,这样您就可以专门检查新帐户的状态,并决定是否可以进行转帐。

    您还必须记住,存款不是帐户上唯一允许的操作。例如,也有提款。如果有人提款,则无需检查您的帐户是否有转账的可能性,因为我们可以确定现在这笔钱甚至更少。因此,我们希望在存款时醒来,但又不想在提款时醒来。我们需要以某种方式将它们分开。这是发挥条件的地方。

    条件是实现Condition接口(interface)的对象。这只是一个抽象,允许您将锁定/等待逻辑划分为多个部分。在我们的例子中,我们可能有两个条件:一个用于增加帐户余额,另一个用于减少余额(例如,如果有人在等待将银行帐户清零以将其关闭):
    sufficientFunds = bankLock.newCondition();
    decreasedFunds = bankLock.newCondition();
    

    现在,您无需在循环中进行大量的检查,您可以通过以下方式组织程序:仅当有人向一个帐户存入资金时,您才可以唤醒并检查该帐户:
    private final double[] accounts;
    private Lock bankLock;
    private Condition sufficientFunds;
    
    public void transfer(int from, int to, int amount) {
        bankLock.lock();
        try { 
            while (accounts[from] < amount) {
                sufficientFunds.await();
            }
            // transfer funds ...
            sufficientFunds.signalAll();
        } finally { 
            bankLock.unlock(); 
        }
    }
    
    public void deposit(int to, int amount) {
        bankLock.lock();
        try {
            // deposit funds...
            sufficientFunds.signalAll();
        } finally {
            bankLock.unlock();
        }
    }
    

    因此,让我们看看这里发生了什么,并回答您的问题:
  • transfer()试图获取bankLock。如果有人已经持有此锁,则在释放该锁之前,您的线程将被另一个线程阻塞。
  • 当另一个线程释放该锁时,您将获得该锁并可以检查帐户的状态。如果没有足够的钱,您决定坐下来等待某人将钱存入帐户,因此您致电sufficienFunds.await()。它使您的线程等待发生的事情。您决定这样做,而不仅仅是因为另一个线程而被阻塞,那就是阻塞和等待之间的区别。但是您是对的,在两种情况下您的线程都没有运行,因此区别更合乎逻辑,而不是技术上的区别。
  • await()上创建的条件下调用bankLock会释放此锁,并使该锁可用于其他线程来获取和执行操作。如果不释放锁,线程将阻止存储区中的所有操作并导致死锁。
  • 现在,当有人对增加金额的帐户进行任何更改时,它会通知sufficientFunds条件,我们的传输线程醒来,循环可以再次进行检查。在这里,我们有两件事要看。首先,当我们的线程唤醒时,它会自动重新获取该锁,因此我们可以确保我们可以排他地,安全地进行检查和修改。其次,可能发生的是新的金额仍然不足以进行转帐(在某种情况下进行信号通知仅表示发生了可能会改变您正在等待的状态,但不能保证该状态的事情)。在这种情况下,我们必须等待下一次存款。这就是为什么我们必须使用while循环,而不是简单的if的原因。
  • 当最终帐户中的金额足够时,我们进行转帐。并且我们在进行转帐后也称为sufficienFunds.signalAll(),因为我们增加了to帐户中的金额。如果某个其他线程正在等待该帐户的注资,则它将获得该锁并可以开始工作。

  • 因此,使用锁和条件可以使您以安全有效的方式组织多线程程序。

    关于java - 为什么要使用条件,“阻塞”和“等待”之间有什么区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39050057/

    相关文章:

    mysql - 阻止 MariaDB 锁定进程

    java - 在 play 框架中生成的源代码(尤其是 thrift)

    java - 如何将 lucene documentid 映射到 Hibernate 搜索实体字段

    java - Java监视器和线程并发

    java - 是否允许在 MBean 内处理线程?

    locking - 访问 2007,使用 "edit"按钮或切换锁定表单加载时的所有字段

    java - Android USB 主机异步批量传输示例

    java - 在 Ant 中,如何指定文件名中带有逗号的文件?

    java - 几个Runnable完成后的返回值

    C# - 从字典中获取资源时锁定