同步包括 3 个部分:原子性、可见性和排序
同步块是非常粗略的同步级别。它按照您的预期强制执行可见性和排序。但是对于原子性,它并没有提供太多的保护。原子性需要程序的全球知识而不是本地知识。(这使得多线程编程非常困难)
假设我们有一个Account
具有方法deposit
和的类withdraw
。它们都是基于这样的私有锁同步的:
class Account {
private Object lock = new Object();
void withdraw(int amount) {
synchronized(lock) {
// ...
}
}
void deposit(int amount) {
synchronized(lock) {
// ...
}
}
}
考虑到我们需要实现一个处理传输的更高级别的类,如下所示:
class AccountManager {
void transfer(Account fromAcc, Account toAcc, int amount) {
if (fromAcc.getBalance() > amount) {
fromAcc.setBalance(fromAcc.getBalance() - amount);
toAcc.setBalance(toAcc.getBalance + amount);
}
}
}
假设我们现在有 2 个帐户,
Account john;
Account marry;
如果Account.deposit()
和Account.withdraw()
仅使用内部锁锁定。当我们有 2 个线程工作时,这将导致问题:
// Some thread
void threadA() {
john.withdraw(500);
}
// Another thread
void threadB() {
accountManager.transfer(john, marry, 100);
}
因为两者都可以同时threadA
运行threadB
。并且线程B完成条件检查,线程A退出,线程B再次退出。这意味着即使他的账户没有足够的钱,我们也可以从约翰那里提取 100 美元。这将破坏原子性。
您可能会建议:为什么不添加withdraw()
anddeposit()
呢AccountManager
?但是根据这个提议,我们需要创建一个多线程保险箱Map
,将不同帐户映射到它们的锁。我们需要在执行后删除锁(否则会泄漏内存)。我们还需要确保没有其他人Account.withdraw()
直接访问。这将引入许多微妙的错误。
正确和最惯用的方法是在Account
. 并让AccountManager
使用锁。但是在这种情况下,为什么不直接使用对象本身呢?
class Account {
synchronized void withdraw(int amount) {
// ...
}
synchronized void deposit(int amount) {
// ...
}
}
class AccountManager {
void transfer(Account fromAcc, Account toAcc, int amount) {
// Ensure locking order to prevent deadlock
Account firstLock = fromAcc.hashCode() < toAcc.hashCode() ? fromAcc : toAcc;
Account secondLock = fromAcc.hashCode() < toAcc.hashCode() ? toAcc : fromAcc;
synchronized(firstLock) {
synchronized(secondLock) {
if (fromAcc.getBalance() > amount) {
fromAcc.setBalance(fromAcc.getBalance() - amount);
toAcc.setBalance(toAcc.getBalance + amount);
}
}
}
}
}
简而言之,私有锁不适用于稍微复杂的多线程程序。
(转自https://stackoverflow.com/a/67877650/474197)