我已经搜索过this one和this one之类的一些SO问题,但我的结论却无法得出真正的结论。所以,我会说出来:
我有以下嵌套的地图结构概念,适用于多线程环境:
Map<Integer, HashMap<String, AtomicInteger>> bufferMap = new ConcurrentHashMap<Integer, HashMap<String, AtomicInteger>>(2);
这个“缓冲区映射”基本上应该存储一些每小时计数器(AtomicInteger
s),由特定的String
密钥识别/访问。因此,缓冲区映射的Integer
键实际上是小时数(0 ... 23)。我只打算“缓冲”电流并预先设定下一个小时。为此,有一个每小时运行一次的计时器任务,并执行维护程序 - 如下所示:
private final Map<Integer, HashMap<String, AtomicInteger>> bufferMap;
...
private final java.util.Timer;
private final java.util.TimerTask task = new TimerTask() {
@Override
public void run() {
....
HashMap<String, AtomicInteger> counterMap = bufferMap.get(previousHour);
// now read internalMap's values, and "store/flush" them somewhere
// at this point no thread but this one should access previous hour data
initializeNextHourSlot(); // populate new map entry for the next hour with new AtomicInteger(0) values
bufferMap.remove(previousHour); // clear previous hour, as no longer needed
}
}
现在,多个线程可以随机和/或并行访问此结构,以下列方式增加计数器:
bufferMap.get(currentHour).get(stringKey).incrementAndGet();
由于外部(缓冲区)映射实际上是由不同的(Timer
)线程修改而不是读取它的,所以我认为使用ConcurrentHashMap
是合乎逻辑的。
但是,我怀疑内部(计数器)地图 ...它将始终由计时器线程提前填充(没有其他线程应该访问它至少一个小时),然后将被访问(只读),如上所示,以增加计数器值。
这是一种线程安全方法吗?如果没有,那么可能是另一种建议的数据结构(和/或方法)?
答案 0 :(得分:1)
这是一种线程安全方法吗?
您无法确定线程何时会运行。即使您在需要之前一小时创建了结构,该过程也可能处于休眠状态并仍然无法运行(理论上)
如果没有,那么可能是另一种建议的数据结构(和/或方法)?
更简单的方法是不使用计时器,如果不存在则使用计算。
final AtomicReference<TimedData> ref = new AtomicReference<>();
public void increment(String counter) {
TimedData td = ref.get();
long hour = System.currentTimeMillis() / 3_600_000;
if (td.hour != hour) {
saveData(td); // use back ground thread if needed.
if (!ref.compareAndSet(td, new TimedData(hour))
td = ref.get();
}
td.counterMap.get(counter)
.incrementAndGet();
}
为班级
class TimedData {
final long hour;
final Map<String, AtomicInteger> counterMap = new HashMap<>();
public TimedData(long hour) {
this.hour = hour;
// init the counterMap
}
在这种情况下,后台线程是可选的,它在运行时无关紧要。
答案 1 :(得分:1)
首先,这不安全。您的计时器和编写器可以同时读/写当前小时数据。考虑一下:
bufferMap.get(currentHour).get(stringKey).incrementAndGet();
不是原子的,这意味着你实际上有:
hourlyMap = bufferMap.get(currentHour);
// assume this thread was now suspended by the OS for some time
keyCounter = hourlyMap.get(stringKey);
keyCounter.incrementAndGet();
在我看来,你有两个定时器线程之间可以交换的地图,但除了效率之外,这对上面的问题没有帮助。假设您希望获得所有计数器的连贯快照(这是一个强烈要求),您需要在阅读时排除编写者更新计数器。有一个例子,使用PhaseLock(http://hdrhistogram.org/)在HdrHistogram库记录器类中解决这个确切的问题,来破解你的计数器地图:
public void incKey(String k) {
long criticalValueAtEnter = recordingPhaser.writerCriticalSectionEnter();
try {
activeCounterMap.get(k).incrementAndGet();
} finally {
recordingPhaser.writerCriticalSectionExit(criticalValueAtEnter);
}
}
private void sampleCounters() {
try {
recordingPhaser.readerLock();
// ...swap your maps here...
recordingPhaser.flipPhase(500000L /* yield in 0.5 msec units if needed */);
} finally {
recordingPhaser.readerUnlock();
}
}
我相信还有其他类似的锁可以用来实现相同的效果。你想要的是许多作家优先于我认为的单一读者。