以下并发代码中是否存在竞赛

问题描述 投票:0回答:1

假设我有不同的接口实现:

interface IDataManager
{
  Integer GetData(string key); // returns null if data is not found
  void UpdateData(string key, int value);
}

该接口的所有实现都已正确同步。 另外,我有一个类来查询存储在

IDataManager
实现中的数据:

public class DataProvider {
    private final List<IDataManager> dataManagers; // immutable, set from constructor

    public DataProvider(List<IDataManager> dataManagers) {
        this.dataManagers = dataManagers;
    }

    public List<Integer> getData(String key) {
        List<Integer> found = new ArrayList<>();

        for (int i = 0; i < dataManagers.size(); i++) {
            IDataManager dataManager = dataManagers.get(i);
            Integer data = dataManager.getData();
            
            if (data != null) {
                found.add(data);
            }
        }

        return found;
    }
}

这里需要注意的是,当某些线程执行

DataProvider.GetData()
时,其他线程可能会使用
IDataManager
更新各个
IDataManager.UpdateData
实现。
IDataManager
的每个实例都正确同步,但它们单独更改的事实令我担心。 在迭代它们之前,我是否应该首先获取
IDataManager
的所有实例的锁(假设它们提供了这样的机会)?

我这么问是因为它让我想起了我在《Java并发实践》一书中看到的一个例子,清单4.10:

public class NumberRange {
 // INVARIANT: lower <= upper
 private final AtomicInteger lower = new AtomicInteger(0);
 private final AtomicInteger upper = new AtomicInteger(0);

 public void setLower(int i) {
    // Warning-- unsafe check-then-act
    if (i > upper.get())
    throw new IllegalArgumentException("can’t set lower to " + i + " > upper");
    lower.set(i);
 }

 public void setUpper(int i) {
    // Warning-- unsafe check-then-act
    if (i < lower.get())
    throw new IllegalArgumentException("can’t set upper to " + i + " < lower");
    upper.set(i);
 }

 public boolean isInRange(int i) {
    return (i >= lower.get() && i <= upper.get());
 }
}

这里,在方法

isInRange
中,
lower
upper
的值是独立读取的,而不是在锁下一起读取。这让我想起了前面例子中我的
DataProvider.GetData
方法。

java multithreading concurrency synchronization
1个回答
0
投票

在迭代 IDataManager 的所有实例之前,我是否应该首先获取它们的锁(假设它们提供了这样的机会)?

这取决于您的要求。

如果不获取锁,一个线程中的

IDataManager.UpdateData()
可能会更改数据,而
DataProvider.getData()
则可能会迭代另一线程中的
IDataManager

通过获取锁

IDataManager.UpdateData()
DataProvider.getData()
以原子方式进行操作。

两种选择都很好:第一个具有更高的性能,第二个提供更严格的保证。

特别是第一个选项允许这样做:

IDataManager dm1 = ...;
IDataManager dm2 = ...;
DataProvider p = new DataProvider(List.of(dm1, dm2));

initialThread() {
  dm1.UpdateData("a", 10);
  dm2.UpdateData("a", 20);
}

void thread1() {
  dm1.UpdateData("a", 11);
  dm2.UpdateData("a", 22);
}

void thread2() {
  System.out.println(p.getData());
}

输出:

10,22

而第二个选项不允许这样做。

© www.soinside.com 2019 - 2024. All rights reserved.