数据如何 stored/accessed 并防止地图中的竞争条件,java
How data is stored/accessed and preventing race conditions in maps, java
我们有这样的案例。
class A{
class foo{
//Map with a lot of entries
private HashMap<String,String> dataMap;
public updater(){
// updates dataMap
// takes several milliseconds
}
public someAction(){
// needs to perform read on dataMap
// several times, in a long process
// which takes several milliseconds
}
}
问题是,someAction和updater都是可以同时调用的,someAction是比较频繁的方法。如果调用更新器,它可以从 dataMap 中替换很多值。我们需要 readAction 的一致性。如果该方法以旧 dataMap 开始,则所有读取都应使用旧 dataMap。
class foo{
//Map with a lot of entries
private HashMap<String,String> dataMap;
public updater(){
var updateDataMap = clone(dataMap); // some way to clone data from map
// updates updateDataMap instead of dataMap
// takes several milliseconds
this.dataMap = updateDataMap;
}
public someAction(){
var readDataMap = dataMap;
// reads from readDataMap instead of dataMap
// several times, in a long process
// which takes several milliseconds
}
}
这样能保证一致性吗?我相信克隆方法将在内存中分配一个不同的区域,并且新的引用将从那里发生。是否会对性能产生影响?还有oldDataMap的内存用完后会不会释放?
如果这是正确的方法,还有其他有效的方法吗?
我相信您的方法会奏效,因为 updater()
的所有更改都将发生在(深)副本中,并且 someAction()
不可见,直到在一次操作中,参考已更新。
我理解你不关心someAction()
看到的是不是最新版的地图内容,只要地图是一致的,也就是不观察而它正在更新中。在这种情况下,您的 someAction()
无法查看不完整的地图。
请注意,最多 1 个线程应该能够调用 updater()
- 两个线程同时调用它意味着只有其中一个线程可以写入更新的地图。我建议进行以下更改:
// no synchronization needed at this level, but volatile is important
private volatile HashMap<String,String> dataMap = new HashMap<>;
// if two threads attempt to call this at once, one blocks until the other finishes
public synchronized updater() {
var writeDataMap = clone(dataMap); // a deep copy
// update writeDataMap - guaranteed no other thread updating
// ... long operation
dataMap = writeDataMap; // switch visible map with the updated one
}
public someAction() {
var readDataMap = dataMap;
// process readDataMap - guaranteed not to change while being read
// ... long operation
}
这里重要的关键字是 volatile,以确保其他线程在 updater()
完成其工作后立即访问更新的映射。使用synchronized只是简单的防止多个updater()
线程相互干扰,多为防御性
如果您想避免复制,您可以使用 java.util.concurrent.locks.ReentrantReadWriteLock
来保护对您的地图的访问。在updater
中使用写锁,在someAction
中使用读锁。
我们有这样的案例。
class A{
class foo{
//Map with a lot of entries
private HashMap<String,String> dataMap;
public updater(){
// updates dataMap
// takes several milliseconds
}
public someAction(){
// needs to perform read on dataMap
// several times, in a long process
// which takes several milliseconds
}
}
问题是,someAction和updater都是可以同时调用的,someAction是比较频繁的方法。如果调用更新器,它可以从 dataMap 中替换很多值。我们需要 readAction 的一致性。如果该方法以旧 dataMap 开始,则所有读取都应使用旧 dataMap。
class foo{
//Map with a lot of entries
private HashMap<String,String> dataMap;
public updater(){
var updateDataMap = clone(dataMap); // some way to clone data from map
// updates updateDataMap instead of dataMap
// takes several milliseconds
this.dataMap = updateDataMap;
}
public someAction(){
var readDataMap = dataMap;
// reads from readDataMap instead of dataMap
// several times, in a long process
// which takes several milliseconds
}
}
这样能保证一致性吗?我相信克隆方法将在内存中分配一个不同的区域,并且新的引用将从那里发生。是否会对性能产生影响?还有oldDataMap的内存用完后会不会释放?
如果这是正确的方法,还有其他有效的方法吗?
我相信您的方法会奏效,因为 updater()
的所有更改都将发生在(深)副本中,并且 someAction()
不可见,直到在一次操作中,参考已更新。
我理解你不关心someAction()
看到的是不是最新版的地图内容,只要地图是一致的,也就是不观察而它正在更新中。在这种情况下,您的 someAction()
无法查看不完整的地图。
请注意,最多 1 个线程应该能够调用 updater()
- 两个线程同时调用它意味着只有其中一个线程可以写入更新的地图。我建议进行以下更改:
// no synchronization needed at this level, but volatile is important
private volatile HashMap<String,String> dataMap = new HashMap<>;
// if two threads attempt to call this at once, one blocks until the other finishes
public synchronized updater() {
var writeDataMap = clone(dataMap); // a deep copy
// update writeDataMap - guaranteed no other thread updating
// ... long operation
dataMap = writeDataMap; // switch visible map with the updated one
}
public someAction() {
var readDataMap = dataMap;
// process readDataMap - guaranteed not to change while being read
// ... long operation
}
这里重要的关键字是 volatile,以确保其他线程在 updater()
完成其工作后立即访问更新的映射。使用synchronized只是简单的防止多个updater()
线程相互干扰,多为防御性
如果您想避免复制,您可以使用 java.util.concurrent.locks.ReentrantReadWriteLock
来保护对您的地图的访问。在updater
中使用写锁,在someAction
中使用读锁。