请解释霍茨曼的“核心Java”中关于同步块的以下声明(第9版,第865页):
Vector的get和set方法是同步的,但这无助于我们。 ..。 然而,我们可以劫持锁: 公共无效转移(向量帐户,int从,int到,int数量){同步(帐户){accounts.set(从,accounts.get(从)到数量);accounts.set(到,accounts.get( to ) +int);}//.} 这种方法可以工作,但它完全依赖于如下事实: Vector对其所有mutator方法都使用了内部锁。
为什么同步依赖于上述事实?如果线程A拥有帐户上的锁,任何其他线程都不能获得相同的锁。它不依赖于方法所使用的锁。
我能想到的唯一可能的解释是下面的解释。让线程A拥有锁定帐户。如果Vector为它的set/get使用了另一个锁,那么Thread A必须获得一个额外的锁才能通过set/get,这是不可能的(线程可以同时持有两个不同的锁吗?)
这个解释在我看来似乎不可信,但我没有别的东西。我遗漏了什么?
发布于 2013-08-23 00:02:44
如果线程A拥有帐户上的锁,任何其他线程都不能获得相同的锁。它不依赖于方法所使用的锁。
但是,如果向量使用一个完全无关的锁来实现自己的同步,那么您的锁对象将是毫无意义的。这样的代码不会同步:
x.transfer(vector, 100, 100, 100); // uses your lock
vector.add(100); // uses Vector's own, unrelated lock如果您的所有代码都通过您自己的方法(使用您的锁),并且没有人直接访问向量的方法,那么您就没事了。(但这样您就根本不需要使用向量的内置同步,并且可以使用ArrayList)。
只有当所有相关的代码路径都使用这些锁时,才能工作。通常涉及多个方法,它们需要使用相同的一组锁正确地“相互交谈”。应该由程序员来确保.
让线程A拥有锁定帐户。如果Vector为它的set/get使用了另一个锁,那么Thread A必须获得一个额外的锁才能通过set/get,这是不可能的(线程可以同时持有两个不同的锁吗?)
这不是不可能的,线程A可以持有任意数量的锁。但是,在上面的访问模式中,线程A持有第一个锁是没有意义的,因为线程B在只使用内置向量锁时甚至不会试图锁定它。
发布于 2013-08-23 00:04:32
这种方法可以工作,但它完全依赖于如下事实: Vector对其所有mutator方法都使用了内部锁。
这是为了解释您是在锁定accounts,而Vector是锁定在同一个对象上。这意味着对accounts Vector进行更改的其他线程将被锁定,并且不会出现争用条件。
如果您没有共享这个锁,那么您将有一个竞争条件,因为在该synchronized块中有4个操作正在进行:
由于有锁,我假设其他线程正在修改后台的其他帐户。如果Vector假设更改了它们的内部锁定策略,其他线程可能会在此过程中对from或accounts进行更改,从而导致记帐混乱。例如,如果from帐户在#1和#2之间增加,那么这个值就会因为传输而被覆盖。
依赖于这样一个类的内部锁定范式是非常糟糕的形式。这意味着如果Vector决定改变它的锁定机制(是的,我知道它不会),那么您的代码就会有一个竞争条件。更有可能的是,另一个程序员(或未来的您)决定将accounts更改为使用不同锁定机制的不同Collection,代码就会中断。您不应该依赖于类的内部行为,除非它被专门记录为类的内部行为。
如果您需要防范这样的竞争条件,那么您应该在访问synchronized的过程中执行一个Vector锁。
顺便说一句,你不应该再使用Vector了。如果需要同步列表,请使用Collections.synchronizedList(new ArrayList<Double>);或Java5中引入的新并发类之一。
发布于 2013-08-23 00:08:00
我还没有读过这本书,但我想他们想说的是,每个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);
}如果两个线程(T2和T3)同时使用余额$1,500的from帐户(A1)和不同的to帐户(A2和A3)调用余额$0的transfer,会发生什么情况?比如100美元给A2,1,200美元给A3?
accounts.get('A1'),减去100。Result= 1,400,但还没有存储回。accounts.get('A1') (仍然是1,500),减去1,200。Result= 300accounts.get('A1') (如果执行)将产生300。accounts.get('A1')将产生1,400个。accounts.get('A2') (值0),添加100,并将其存储回。accounts.get('A2') (如果执行)将产生100。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拥有帐户上的锁,任何其他线程都不能获得相同的锁。它不依赖于方法所使用的锁。
这正是关键所在:所有需要对数据结构进行互斥访问的类都需要就它们要去synchronize的对象达成一致。这个案子非常简单。在更复杂的情况下,选择这个对象并不容易。在许多情况下,如果将一个对象特权于其他对象,则会创建一个特殊的“锁定”对象。但是这有一个限制:整个数据结构一次只能更新一次。在存在问题的应用程序中,需要定义更详细的锁定策略,开发人员很难确定哪些对象可以被锁定,哪些对象应该在每种可能的情况下锁定。这些战略还需要注意陷入僵局的可能性和种族状况。
https://stackoverflow.com/questions/18392604
复制相似问题