同步包括 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() 和deposit() 添加到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);
}
}
}
}
}
简单地说,私有锁不适用于稍微复杂的多线程程序。