我还没有读过这本书,但我认为他们想说的是每个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?说出100美元到A2和1200美元到A3?
- 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')
(如果执行)将产生1200。
于是,我们开始A1
+ A2
+ A3
= 1500 + 0 + 0 = 1500,而做这些内部转账后,我们有A1
+ A2
+ A3
= 1400 + 100 + 1200 = 2,700元。显然有些东西在这里不起作用。什么?在步骤1和步骤4之间.T2保持A1
的平衡,并且没有(不能)检测到T3
也在减少它,至少在概念层面。
这当然不会发生在每次运行中。但这是邪恶的,因为如果隐藏在几十万行代码中,再现(以及发现和重新测试)这个问题将会很困难。
但是请注意,即使accounts
方法从未同时被调用,上述情况也会发生。甚至没有尝试。实际上,accounts
作为Vector
没有损坏,但它作为我们的数据结构已损坏。
这就是那句
get和Vector类的设置方法是同步的,但这并不能帮助我们。
指的是。
如果说对于提出的解决方案是
这种方法的工作原理,但它是完全依赖于Vector类使用其所有mutator方法的内部锁的事实。
,作者肯定假设除了转移方法之外,account
还有其他用途。例如:添加账户,删除账户等。从这个意义上说,幸运的是,这些方法也在矢量上同步。如果它们在一个内部对象中同步,则系统的开发人员需要将accounts
包装在辅助同步层中,这次是基于accounts
本身或任何其他常见对象。
最后,关于
如果一个线程A拥有的账户锁定,没有其他线程可以获取同样的锁。它不依赖于Vector用于其增变器方法的锁。
这可能恰恰是这样一个观点:所有需要对数据结构进行互斥访问的类需要就他们将要访问的对象达成一致synchronize
。这个案例非常简单。在更复杂的情况下,选择这个对象不是微不足道的。在许多情况下,相反,如果将某个对象放在其他对象上,则会创建一个特殊的“锁定”对象。但是这有一个限制:整个数据结构一次只能更新一次。在出现这种问题的应用程序中,需要定义更详细的锁定策略,开发人员可能难以确定哪些对象可能被锁定,以及哪些对象应锁定在每种可能的情况下。这些策略还需要关注死锁和竞态条件的可能性。
“如果向量决定改变它的锁定机制”虽然这是不可能的。它如何同步显然是API的一部分,他们不会打破这一点。 – Thilo
在Vector的Javadoc中,它提到了你可以依赖所有被同步的方法。如果将'Vector'切换到使用内部的'private final Object lock',它们不会相同吗?我知道他们不会改变它,但这是我反对的模式。 @Thilo。 – Gray
@Thilo实际上,他们在书中进一步提到Vector并未做出这样的承诺。 –