有一个任务。有必要实现多线程应用程序,而不使用同步。
有一个带泊位的港口(1 个泊位 - 1 艘船)。有货船和仓库。在泊位,船舶可以将货物转移到仓库或另一艘船上。
我简单地尝试在每个函数中将 ReentrantLock 放在开头和结尾。显然我误解了它是如何工作的。使用同步,您可以简单地将它挂在第一次进入函数的每个线程上,并且一切正常,但没有它 - 死胡同。谁能建议如何正确使用锁或其他方式来实现这样的应用程序?
一段代码:
private ReentrantLock lock = new ReentrantLock();
public boolean tryTransfer(Ship ship) {// ф-ия пытается добавить груз(cargo) в хранилище
boolean flag; // capacity - объём хранилища
lock.lock();
if (capacity + ship.getCargo()>maxCapacity) {
flag = false;
} else
{
capacity+=ship.getCargo();
ship.setCargo(0);
flag = true;
}
lock.unlock();
return flag;
}
ReentrantLock在方法内创建并在同一个地方使用它们(这可以在类Dock和中看到Ship)。如果您不将这些锁传递给其他地方,那将没有任何意义。存储ReentrantLock在字段中。您的实现
Storage#tryTransfer(在 GitHub 上,不在此处)几乎是正确的。您正确地限制了对Storage. 但是,在此方法中,您无法正确使用Ship- 方法Ship#getCargo并且Ship#setCargo未同步。在这种情况下,您需要同时锁定Storage, 和Ship。像这样的东西(通过类比,你可以重做其他所有事情):如果第 2 点已经在and中,为什么
Storage#tryTransfer我还要锁定它?事实上,当多个线程同时使用同一个实例时,这种情况是可能发生的。那些。例如,我们调用并接收到 10。我们希望当我们调用 时,cargo 仍然等于 10。但是,如果我们的调用之间没有额外的同步,并且某个其他线程本身可以使用其他值调用,我们得到 ,在我们打电话时,货物不是原来的 10,而是例如 12。Ship#getLockShip#getCargoShip#setCargoShipShip#getCargoShip.setCargo(0)Ship#getCargoShip#setCargoShip#setCargoShip#setCargo(0)更新
Ship#run忘记了锁Dock。Ship#run你调用ReentrantLock#lock里面try-finally,而不是在它之前。这是无法做到的,因为try-finally在获取锁之前它可能会被中断(例如,由于异常),但ReentrantLock#unlock它仍然会被调用。Ship#run打电话Dock#setCurrentShip?直接放进去不就好Ship了Dock#shipsQueue,然后码头自己先搞清楚要处理哪艘船?Dock#run通话后ReentrantLock#lock立即做try-finally。正常情况下甚至会发生异常System.out.println(),导致您的异常ReentrantLock#unlock不会被调用。Dock#run您首先检查Dock#currentShip然后才取锁。情况并非总是如此。如果它Dock#currentShip没有突出,那么这将是正常的,但是你有Dock#getCurrentShipandDock#setCurrentShip,这意味着需要同步它们。Dock#setShipsQueue. 不是任何人都可以拿走并替换如此重要的收藏品的情况。最好完全在内部创建它Dock(而不是从外部接受它)并且不要将其提供给任何人 - 只需添加 publicDock#addShip和Dock#removeShip. 好吧,Dock#isFree您可以对其进行修改,使其不返回某些字段的值,而是检查Dock#currentShip和Dock#shipsQueue(尽管您可以自己决定 - 我不知道您的原始想法)。Dock#isFree将始终false在第一次处理任何非空船后返回。在我看来,这里出了点问题——船总有一天会用完=>在这种情况下,码头应该是空的,这不会发生。更新
ReentrantLock#unlock您无需在调用之前先调用ReentrantLock#tryLock。线程已经拥有锁,所以这个检查不会做任何事情。Ship#run您的队列中,您可以无休止地添加一艘船。只需删除循环,您不需要它。Storage#run是完全不正确的。即使您省略了第 1 点,出于某种原因,您也会在之前return和类似的运算符之前释放锁。没有必要这样做 - 块finally总是被执行,即使在returnor之后也是如此continue。