我们为什么要使用条件以及阻止和等待之间的差异

时间:2016-08-20 03:04:23

标签: java multithreading locking conditional-statements

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

  

如果我们只是无条件地锁定和等待,就会陷入僵局:

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

现在,当帐户中的资金不足时我们该怎么办?我们   等到其他线程增加了资金。但这个线程只是   获得了bankLock的独占访问权限,因此没有其他线程拥有   有机会存款。

     

致电时

sufficientFunds.await();
  

现在停用当前线程并放弃锁定。这让我们   我们希望在另一个线程中增加账户余额。

Lock锁定代码,条件放弃锁定,但我不知道什么是条件,为什么不只是在钱不够时简单解锁块? 线程状态阻塞和等待之间的区别是什么? 块:线程无法运行; 等待:线程也无法运行。 有什么不同?

另一个问题:

while (accounts[from] < amount) {
   ...
   sufficientFunds.await();

为什么不写if

if (accounts[from] < amount) {
    ...

3 个答案:

答案 0 :(得分:1)

我认为在这种情况下是synchronized的例子,并且Lock的使用改变了(对于最新的书籍修订,但当然没有出错)。对于synchronized这样的代码,没有问题:

   private final double[] accounts;
   private Condition sufficientFunds;
    public synchronized void transfer(int from, int to, int amount)
    {
         try { 
            while (accounts[from] < amount) {
            // wait . . . } 
        // transfer funds . . . 
         } finally {
            this.notifyAll();
         }
    }

当我们调用此方法时,我们获取了一个锁,但是当我们调用wait()时,我们将其释放并等待调用notifyAll()方法或指定时间结束。醒来后,我们再次尝试获取锁定并再次检查我们的状况..并重复这一系列操作,直到我们获得足够的钱。

这张照片显示了WAITING和BLOCKED之间的区别: enter image description here

我们在这里使用

while (accounts[from] < amount) {

而不是

if (accounts[from] < amount) {

因为我们希望获得方法调用的正面结果,但我们无法保证另一个线程会将所有缺少的资金都提供给我们预期的帐户。 例如,如果您是银行,请致电

reduce(John,1000)

但客户John没有足够的钱(500美元而不是1000美元),如果您的减少操作包含在if而不是while中,您将尝试只等​​待一次。你不幸运,此时银行里没有客户。所以你进入if - 阻止等待一段时间再次锁定,但你帐户的金额没有变化(仍然是500美元)。你不进行重复检查(因为if-block而不是while),你去转移操作,这几乎不是我们真正想要的。

方法sufficientFunds.await();必须在其中执行三个操作:releaseLock(),wait(),lock()。这意味着我们释放锁定以允许另一个线程与银行合作并等待它完成它的操作,之后我们获得锁定以再次检查我们的余额并且如果我们现在获得足够的钱,则计划transfer()

答案 1 :(得分:1)

首先是while块的原因

其中的代码只是等待该行的线程。只编写循环,以便在将来通知线程时(通过任何机制,例如添加了资金并在此之后使用notifyAll的线程),它会再次检查while循环中给出的条件。如果金额仍然大于账户,它将再次进入等待状态并将离开锁定。 这将允许其他线程添加资金,然后再次给予上述线程关注while while条件。 一旦条件失败,它将不会等待并将继续转移资金。因此,将通过解锁状态保持锁定。

阻止和等待之间的区别:

如果我们专注于正确使用等待,我们可以避免被称为阻止的情况。当我们等待时,我们认为代码的编写方式是线程可以正常通知对方而不会产生死锁。 如果thread1正在等待依赖于thread2的条件,并且thread2正在等待取决于thread1的条件,则会发生死锁或阻塞。

答案 2 :(得分:1)

对课程Bank有特殊要求:如果要求将一定数量的资金从一个帐户转移到另一个帐户,并且该来源帐户的资金不足,它必须等到存入足够的钱才能使转移成为可能。你可以运行一个循环,检查每次迭代是否有足够的钱,只有在满足这个条件时才获取锁:

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

但这种做法:

  • 在不断检查时浪费CPU资源(什么都没有 在几小时或几天内存入足够的钱?)
  • 看起来笨重但不是惯用的
  • 并不总是有效(原因超出了这个问题的范围,如果您对此感兴趣,我可以在评论中给出解释的链接)

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

您还必须记住,存款不是帐户允许的唯一操作。例如,还有提款。如果有人提款,则无需检查您的帐户是否有转帐的可能性,因为我们现在肯定知道它的资金更少。因此,我们希望被存款唤醒,但不想在提款时被唤醒。我们需要以某种方式将它们分开。这是条件发挥作用的地方。

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

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

所以,让我们看看这里发生了什么,并回答你的问题:

  1. transfer()正在尝试获取bankLock。如果某人已拥有此锁定,则您的线程将被另一个线程阻止,直到锁定被释放。

  2. 当另一个线程释放锁定时,您获取它并可以检查该帐户的状态。如果它没有足够的资金,您决定坐下来等待有人将钱存入帐户,因此您拨打sufficienFunds.await()。它使您的线程等待以便发生某些事情。你决定这样做,不仅因为另一个线程被阻止,而且阻止等待之间存在差异。但是你是对的,在这两种情况下你的线程都没有运行,所以区别更合乎逻辑,而不是技术性。

  3. await()上创建的条件上调用bankLock会释放此锁定,并使其可供其他线程获取和执行操作。在不释放锁定的情况下,您的线程将阻止银行中的所有操作并导致死锁。

  4. 现在,当有人对增加金额的帐户进行任何更改时,它会通知sufficientFunds条件,我们的转移线程会唤醒并且循环可以进行另一次检查。在这里,我们有两件事要看。首先,当我们的线程唤醒时,它会自动重新获取锁定,因此我们可以确保我们可以完全安全地进行检查和修改。其次,可能会出现新的金额仍然不足以进行转移(在某种情况下发出信号只意味着发生的事情可能会改变您正在等待的状态,但并不能保证那个州)。在这种情况下,我们必须等待下一次存款。这就是为什么我们必须使用while循环,而不是简单的if

  5. 当最终账户中的金额足够时,我们进行转账。我们也会在转帐后致电sufficienFunds.signalAll(),因为我们已经增加了to帐户中的金额。如果某个其他线程正在等待此帐户获得资助,它将获得锁定并可以使其工作。

  6. 因此,使用锁和条件可以安全有效地组织多线程程序。