《核心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();
}
}
现在,当账户中没有足够的钱时,我们该怎么办?我们等待其他线程添加资金。但是这个线程刚刚获得了对锁的独占访问权,所以没有其他线程有机会存款。
打电话的时候
sufficientFunds.await();
当前线程现在被停用并放弃锁。我们希望,这可以让另一个线程增加账户余额。
锁锁定代码,条件放弃锁,但我不知道什么是条件,为什么不只是简单的解锁块时,钱不够?和线程状态之间有什么区别阻塞和等待?块:线程不能运行;等待:线程也不能运行。有什么不同?
另一个问题:
while (accounts[from] < amount) {
...
sufficientFunds.await();
如果
,为什么不写呢?
if (accounts[from] < amount) {
...
首先,while阻塞的原因是:
它里面的代码只是在那一行等待线程。循环的编写方式是,当线程在将来收到通知时(通过任何机制,例如添加资金并在之后使用notifyAll的线程),它会再次检查while循环中给出的条件。如果金额仍然大于帐户,它将再次进入等待条件并离开锁。这将允许其他线程添加资金,然后再次让上述关注的线程尝试while条件。一旦while条件失败,它将不会等待,并将继续转移资金。因此,将通过解锁条件离开锁。
阻塞和等待之间的区别:
如果我们专注于正确使用等待,我们可以避免被称为阻塞的条件。当我们等待时,我们认为代码的编写方式是线程之间会正确地相互通知而不会产生死锁。如果线程1在等待依赖于线程2的条件,而线程2在等待依赖于线程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()
方法调用或指定的时间已过。醒来后,我们再次尝试获取锁并再次检查我们的状况。。重复这一系列动作直到我们拿到足够的钱。
这里我们使用
while (accounts[from] < amount) {
而不是
if (accounts[from] < amount) {
因为我们想得到方法调用的积极结果,但是我们不能保证另一个线程会给我们预期的账户所有丢失的钱。例如,如果你是一家银行,你打电话给
减少(约翰,1000)
但是客户John没有足够的钱(500美元而不是1000美元),如果你的reduce操作包装在if
而不是中,而
你只会尝试等待一次。你们不走运,当时银行里并没有客户。因此,如果你进入
-阻塞等待某个时间并再次获得锁,但你帐户上的金额没有变化(仍然是500美元)。您不需要执行重复检查(因为是if块而不是while),而是转到传输操作,这几乎不是我们真正想要的。
方法
充足的Funds.await();
必须在其中执行三个操作:释放Lock(),等待(),lock()。这意味着我们释放lock以允许另一个线程与bank一起工作,并等待它完成操作,之后我们获得lock来再次检查我们的余额,并在我们现在得到足够的钱时进行计划的转移()
。
对类银行有一个特殊要求:如果要求它从一个账户转账到另一个账户,而源账户上没有足够的钱,它必须等到存款足够时才能转账。您可以运行一个循环,检查每次迭代是否有足够的资金,并仅在满足以下条件时获取锁:
while (true) {
if (accounts[from] >= amount) {
bankLock.lock();
try {
if (account[from] >= amount) {
// do the transfer here...
break;
}
} finally {
bankLock.unlock();
}
}
}
但这种做法:
将CPU资源浪费在不断的检查上(如果没有人在几小时或几天内存入足够的钱,会发生什么?)
看起来笨重而不地道
- 并不总是有效(原因超出了这个问题的范围,如果你感兴趣,我可以在评论中给出解释的链接)
所以,你需要一些机制来告诉你,你只是在等待账户的一些变化。如果没有人往账户里存钱,一次又一次地检查账户里的钱是浪费的。还有更多——一旦有人存钱,你还需要获得锁,这样你就可以专门检查新账户的状态,并决定是否可以转账。
你还必须记住,存款不是账户上唯一允许的操作。例如,还有取款。如果有人取款,没有必要检查你的账户是否有可能转账,因为我们确信现在钱更少了。所以,我们希望在存款时被唤醒,但不希望在取款时被唤醒。我们需要以某种方式将它们分开。这就是条件发挥作用的地方。
条件是一个实现
条件
接口的对象。它只是一个抽象,允许您将锁/等待逻辑分成几个部分。在我们的例子中,我们可以有两个条件:一个是账户余额增加,另一个是减少(例如,如果有人在等待将银行账户归零以关闭它):
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()
正在尝试获取锁
。如果有人已经持有该锁,那么你的线程将被另一个线程阻塞,直到锁被释放。
当另一个线程释放锁时,您将获得它,并可以检查帐户的状态。如果上面没有足够的钱,你决定坐下来等待有人把钱存入账户,所以你叫
sufficienFunds。wait()
。它会让你的线程等待某些事情发生。你决定这么做,不仅仅是因为另一个线程而被阻塞,这就是阻塞和等待的区别。但你是对的,在这两种情况下,你的线程都没有运行,所以区别更符合逻辑,而不是技术。
在
锁
上创建的条件下调用await()。如果不释放锁,线程将阻塞银行中的所有操作并导致死锁。
现在,当有人对账户进行任何增加金额的更改时,它会通知
充足的资金
条件,我们的转移线程唤醒,循环可以进行另一次检查。这里我们有两件事要考虑。首先,当我们的线程唤醒时,它会自动重新获取锁,这样我们就可以确保我们可以独家安全地进行检查和修改。其次,可能会出现新的金额仍然不足以进行转移的情况(对一个条件发出信号仅意味着发生了可能会改变您所等待的状态的事情,但不能保证该状态)。在这种情况下,我们必须等待下一笔存款。这就是为什么我们必须使用当
循环,而不是简单的if
。
当账户中的钱终于足够时,我们进行转账。我们也称之为足够的资金。signalAll()
转账后,因为我们已经将账户中的金额增加到了
账户。如果其他线程正在等待该帐户获得资金,它将获得锁并可以正常工作。
因此,使用锁和条件允许您以安全有效的方式组织您的多线程程序。
我一直在使用ECMAScript 很明显第一步是ECMAScript 下面是我如何实现基本promise的示例/伪代码- 随着时间的推移,我遇到了ECMAScript 同样,这里有一个伪代码,描述了我的异步等待函数的样子- 把语法错误(如果有的话)放在一边,我觉得它们做的事情完全一样。我几乎可以用async、Waities取代我的大部分promise。 当promise做类似的工作时,为什么需要异
问题内容: 之间有什么区别: 和 和 问题答案: 注意事项 : 这个答案仅涵盖了系列和系列之间的时序差异。。 为了这个答案的目的,我将使用一些示例方法: 是一个函数,它需要一个整数毫秒,并返回一个承诺,该承诺将在该毫秒后解析。 是一个函数,它需要一个整数毫秒,并返回一个承诺,该承诺将在该毫秒后被拒绝。 调用将启动计时器。在所有延迟完成后,可以使用等待一些延迟来解决,但请记住它们是同时执行的: 例子
问题内容: 之间有什么区别: 和 和 问题答案: 注意事项: 这个答案仅涵盖了await系列和系列之间的时序差异Promise.all。请务必阅读@mikep的综合答案,其中也涵盖了错误处理方面更重要的区别。 出于此答案的目的,我将使用一些示例方法: res(ms) 是一个函数,它需要一个整数毫秒,并返回一个承诺,该承诺将在该毫秒后解析。 rej(ms) 是一个函数,它需要一个整数毫秒,并返回一个
问题内容: 我只是想知道为什么我们通常在两个布尔之间使用逻辑 而不是按位,尽管它们都运行良好。 我的意思是,请看以下内容: | | 我们可以|代替使用||吗?与&和相同&&。 问题答案: 如果使用和形式,而不是这些运算符的和形式,则Java不会费心地单独评估右手操作数。 多数情况下,是否要缩短评估是一个问题。 说明短路好处的一个好方法是考虑以下示例。 正如Jeremy和Peter提到的,短路的另一
问题内容: 我开始使用RxJS,但我不明白为什么在此示例中我们需要使用类似or 的函数;数组的数组在哪里? 如果有人可以直观地解释正在发生的事情,那将非常有帮助。 问题答案: 当您有一个Observable的结果是更多Observable时,可以使用flatMap。 如果您有一个由另一个可观察对象产生的可观察对象,则您不能直接过滤,缩小或映射它,因为您有一个可观察对象而不是数据。如果您生成一个可观
我正在尝试通过对象读取命令。为了检查输入语法,我使用<code>sc。hasNext()(对于缺少命令的情况)。它已经在很多情况下运行良好,但现在我看到了JavaAPI中描述的“MAY block and wait for Input”的情况。 方法何时阻塞,我如何控制它?有趣的是,在街区前的3个案例中,它工作得非常好。此外,JavaAPI还将描述为检查是否存在另一个Input的正确方法,从而使方