【问题标题】:CoreJava 11th Ed Threading question on client-side locking (synchronized block)关于客户端锁定(同步块)的 CoreJava 第 11 版线程问题
【发布时间】:2021-07-04 21:23:54
【问题描述】:

我正在阅读以下关于为什么不推荐客户端锁定的部分:

“有时,程序员使用对象的锁来实现附加
原子操作——一种称为客户端锁定的做法。考虑一下,对于
例如 Vector 类,它是一个方法同步的列表。
现在假设我们将银行余额存储在 Vector 中。这里是
传输方法的幼稚实现:

public void transfer(Vector accounts, int from, int to, int amount) //
{
accounts.set(from, accounts.get(from) - 金额);
accounts.set(to, accounts.get(to) + amount);
System.out.println(. . .);
}
Vector类的get和set方法是同步的,但是那个
对我们没有帮助。线程完全有可能在
中被抢占 第一次调用 get 完成后的 transfer 方法。另一个
然后线程可以将不同的值存储到相同的位置。但是,我们
可以劫持锁:

公共无效转移(向量帐户,int from,int to,int amount)
{
同步(帐户)
{
accounts.set(from, accounts.get(from) - 金额);
accounts.set(to, accounts.get(to) + amount);
}
System.out.println(. . .);
}

这种方法有效,但它完全取决于 Vector
类对其所有的 mutator 方法使用内部锁
。但是,这是
真的是事实吗? Vector 类的文档没有这样的
承诺。你要仔细研究源代码,希望以后
版本不引入不同步的变异器。如您所见,客户端-
侧锁非常脆弱,一般不推荐。”

问题:
既然transfer方法中的synchronized(accounts)已经获得了accounts的内在锁,为什么依赖vector类的所有mutator方法都使用了内在锁(以粗斜体突出显示?

【问题讨论】:

标签: java java-8 java-11


【解决方案1】:

如果 accounts Vector 的唯一突变发生在 transfer 方法中,那么 Vector 同步它的突变器就无关紧要了。

但是通过锁定与现有突变方法(即Vector)相同的对象,我们可以防止Vector 上的任何其他突变操作。

这不仅仅是由另一个线程完成的传输,它可能会破坏我们的数据,而且,例如,在我们读取了to 帐户的余额之后,在我们设置它之前对其进行了存款。

正如 Holger 指出的那样,一旦您在一个线程中发生突变并在另一个线程中读取,如果您想要一致的数据视图,即使读取操作也需要同步。

正如 Core Java 所建议的,最好封装您想要保护的数据,例如(玩具示例)

public class Accounts {
    private final List<Integer> accounts = new ArrayList();

    public synchronized void transfer(int from, int to, int amount) {
        accounts.set(from, accounts.get(from) - amount);
        accounts.set(to, accounts.get(to) + amount);
    }
    
    public synchronized void deposit(int to, int amount) {
        accounts.set(to, accounts.get(to) + amount);
    }
    
    public synchronized List<Integer> getAccountsSnapshot() {
        // don't return our internal data structure, make a defensive copy
        return new ArrayList(accounts);
    }
}

返回我们的数据副本执行两个功能:

  • 我们不提供对内部数据的引用,因此客户端无法直接修改 ArrayList 中的值,除非使用我们提供的 API。
  • 客户获得一致的账户余额快照,因此他们可以对所有账户求和,并获得在他们调用getAccountsSnapshot时有效的总数。否则,在他们取总时进行修改可能意味着他们得到了“现实生活”中从未发生过的总数。

【讨论】:

  • 让我消化这几天......会更新你。感谢你的帮助。谢谢
  • 虽然代码示例正确,但解释具有误导性,因为它只谈论“mutator 方法”,而事实上,对数据的每次访问都必须使用正确的同步。因此对于原始代码示例,Vector 的读取方法使用相同的同步非常重要。如前所述,此答案的代码示例通过完全封装列表,仅通过声明为synchronizedgetAccountsSnapshot() 提供读取访问权限并返回快照,从而正确地做到了这一点。
猜你喜欢
  • 1970-01-01
  • 2021-08-13
  • 1970-01-01
  • 1970-01-01
  • 2017-07-10
  • 1970-01-01
  • 2019-09-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多