我没有读过这本书,但我认为他们想说的是每个Vector
方法都是同步的(独立的),即使这样可以防止Vector
损坏,它也不能保护它可以存储在其中的信息(尤其是因为业务规则、数据模型、数据结构设计,或者你想怎么称呼它们)。
示例:方法transfer
的实现很天真,相信如果它Vector
的set
方法是同步的,那么一切都很好。
public void transfer(Vector<Double> accounts, int from, int to, int amount) {
accounts.set(from, accounts.get(from) - amount);
accounts.set(to, accounts.get(to) + amount);
}
如果 2 个线程(T2 和 T3)同时调用transfer
相同的from
帐户(A1),余额为 1,500 美元,不同的to
帐户(A2 和 A3),余额为 0 美元,会发生什么情况?对 A2 说 100 美元,对 A3 说 1,200 美元?
- T2:
accounts.get('A1')
检索并减去100。结果= 1,400,但尚未存储回来。
- T3:
accounts.get('A1')
检索(仍为 1,500)并减去 1,200。结果= 300
- T3:将结果存储回来:(
accounts.get('A1')
如果执行)将产生 300。
- T2:存储先前计算的结果。如果执行,
accounts.get('A1')
将产生 1,400。
- T2:
accounts.get('A2')
被检索(值 0),添加 100,然后存储回来。 accounts.get('A2')
(如果执行)将产生 100。
- T3:
accounts.get('A3')
检索(值 0),添加 1,200,然后存储回来。 accounts.get('A3')
(如果执行)将产生 1,200。
所以,我们从A1
+ A2
+ A3
= 1,500 + 0 + 0 = 1,500 开始,在进行这些内部传输之后,我们有A1
+ A2
+ A3
= 1,400 + 100 + 1,200 = 2,700。显然有些东西在这里不起作用。什么?在步骤 1 和 4 之间。T2 保持A1
平衡,并且没有(无法)检测到这T3
也在减少它,至少在概念上是这样。
当然,这不会发生在每次运行中。但这是伪装的邪恶,因为如果隐藏在几十万行代码中,重现(以及查找和重新测试)问题将很困难。
但是,请注意,即使accounts
从未同时调用方法,也会发生上述情况。甚至没有尝试。事实上,accounts
它并没有被破坏为Vector
,而是被破坏为我们的数据结构。
这就是这句话
Vector 类的 get 和 set 方法是同步的,但这对我们没有帮助。
指。
说尊重提出的解决方案
这种方法有效,但它完全依赖于 Vector 类对其所有 mutator 方法使用内部锁这一事实。
,作者肯定假设除了转移方法还有其他用途account
。示例:添加帐户,删除帐户等。从这个意义上说,幸运的是这些方法也在向量上同步。如果它们在内部对象中同步,则系统开发人员将需要包装accounts
在基于辅助同步层的内部,这一次,accounts
它本身或任何其他公共对象。
最后,关于
如果线程 A 拥有帐户锁,则没有其他线程可以获取相同的锁。它不依赖于 Vector 用于其 mutator 方法的锁。
这可能正是重点:所有需要互斥访问数据结构的类都需要就它们要访问的对象达成一致synchronize
。这个案子非常简单。在更复杂的情况下,这个对象的选择并不是微不足道的。在许多情况下,如果将一个对象特权于其他对象,则会创建一个特殊的“锁定”对象。但这有一个限制:整个数据结构一次只能更新一次。在存在此问题的应用程序中,需要定义更精细的锁定策略,开发人员可能难以确定哪些对象可以被锁定,以及在每种可能的情况下应该锁定哪些对象。这些策略还需要注意死锁和竞争条件的可能性。