假设我有不同的接口实现:
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
方法。
在迭代 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
而第二个选项不允许这样做。