java并发性:多个编写器,一个读取器
我需要在我的软件中收集一些统计数据,我正在努力使其快速准确,这对我来说并不容易 首先,到目前为止,我的代码包含两个类,一个StatsService和一个StatsHarvesterjava并发性:多个编写器,一个读取器,java,concurrency,Java,Concurrency,我需要在我的软件中收集一些统计数据,我正在努力使其快速准确,这对我来说并不容易 首先,到目前为止,我的代码包含两个类,一个StatsService和一个StatsHarvester public class StatsService { private Map<String, Long> stats = new HashMap<String, Long>(1000); public void notify ( String key ) { Long va
public class StatsService
{
private Map<String, Long> stats = new HashMap<String, Long>(1000);
public void notify ( String key )
{
Long value = 1l;
synchronized (stats)
{
if (stats.containsKey(key))
{
value = stats.get(key) + 1;
}
stats.put(key, value);
}
}
public Map<String, Long> getStats ( )
{
Map<String, Long> copy;
synchronized (stats)
{
copy = new HashMap<String, Long>(stats);
stats.clear();
}
return copy;
}
}
现在我想我将使用ConcurrentHashMap,因为它提供了很好的性能,同时也很容易理解
谢谢大家的意见!
Janning为什么不使用
java.util.concurrent.ConcurrentHashMap
?它在内部处理所有事情,避免了地图上无用的锁,并为您节省了大量工作:您不必关心get和put上的同步
从文件中:
一个哈希表,支持检索的完全并发性和更新的可调整预期并发性。这个类遵循与Hashtable相同的功能规范,并且包括与Hashtable的每个方法对应的方法版本但是,即使所有操作都是线程安全的,检索操作也不需要锁定,也不支持以阻止所有访问的方式锁定整个表
您可以指定其并发级别:
更新操作之间允许的并发性由可选的concurrencyLevel构造函数参数(默认值16)指导,该参数用作内部大小调整的提示。该表是内部分区的,以尝试允许指定数量的并发更新,而不会产生争用。由于哈希表中的位置基本上是随机的,因此实际的并发性会有所不同理想情况下,您应该选择一个值,以容纳将同时修改表的尽可能多的线程。使用比您需要的值高得多的值可能会浪费空间和时间,而低得多的值可能会导致线程争用。但在一个数量级内的高估和低估通常不会产生太大的影响。当已知只有一个线程将修改,而所有其他线程将只读取时,值为1是合适的。此外,调整此哈希表或任何其他类型的哈希表的大小是一个相对缓慢的操作,因此,如果可能,最好在构造函数中提供预期表大小的估计值
正如评论中所建议的,请仔细阅读的文档,尤其是当它说明了原子操作或非原子操作时
为了保证原子性,你应该考虑哪些操作是原子的,从<代码> CONTRONMODMAP/<代码>界面中你会知道:
V putIfAbsent(K key, V value)
V replace(K key, V value)
boolean replace(K key,V oldValue, V newValue)
boolean remove(Object key, Object value)
可以安全地使用。如果我们忽略了收获部分而专注于编写,那么程序的主要瓶颈是统计数据被锁定在非常粗糙的粒度级别。如果两个线程想要更新不同的密钥,它们必须等待 如果您事先知道密钥集,并且可以预初始化映射,以便在更新线程到达时确保密钥存在,那么您将能够锁定累加器变量而不是整个映射,或者使用线程安全的累加器对象 有些map实现不是自己实现的,而是专门为并发性设计的,可以为您实现更细粒度的锁定
不过需要注意的一点是统计数据,因为您需要大致同时锁定所有蓄能器。如果使用现有的并发友好映射,则可能存在用于获取快照的构造 我建议看一下Java的util.concurrent库。我认为您可以更干净地实施此解决方案。我想你根本不需要地图。我建议使用。每个“生产者”都可以自由地写入此队列,而无需担心其他人。它可以将一个对象放在队列中,其中包含用于统计的数据
收割机可以使用队列不断地提取数据并对其进行处理。然后,它可以根据需要进行存储。正如jack所回避的那样,您可以使用java.util.concurrent库,该库包括ConcurrentHashMap和AtomicLong。您可以将原子长度放入,如果没有,则可以增加该值。由于AtomicLong是线程安全的,因此您可以增加变量,而不必担心并发性问题
public void notify(String key) {
AtomicLong value = stats.get(key);
if (value == null) {
value = stats.putIfAbsent(key, new AtomicLong(1));
}
if (value != null) {
value.incrementAndGet();
}
}
这应该是快速和线程安全的
编辑:重构很慢,所以最多只有两次查找。您有没有研究过?您可以使用它来安排编写器,这些编写器都可以写入并发集合,例如@Chris Dail提到的
ConcurrentLinkedQueue
。您可以根据需要单独安排作业从队列中读取,Java SDK应该可以处理几乎所有的并发问题,不需要手动锁定。Chris Dail的答案看起来是一个不错的方法
另一种选择是使用并发的Multiset
。房间里有一个。您可以按如下方式使用它:
private Multiset<String> stats = ConcurrentHashMultiset.create();
public void notify ( String key )
{
stats.add(key, 1);
}
private Multiset stats=ConcurrentHashMultiset.create();
公共无效通知(字符串键)
{
添加(键,1);
}
查看,这是使用
ConcurrentHashMap
实现的,并使用putIfAbsent
和replace
的三参数版本来检测并发修改并重试。解决问题的另一种方法是通过线程限制来利用(微不足道的)线程安全性。基本上创建一个单一的背景线程,负责阅读和写作。在可扩展性和简单性方面,它具有相当好的特性
其思想是,不是所有线程都试图直接更新数据,而是生成一个“更新”任务供后台线程处理。同一线程也可以执行读取任务,前提是处理更新的延迟是可以容忍的
这种设计非常好,因为线程不再需要竞争锁来更新数据,而且映射是受限的
public void notify(String key) {
AtomicLong value = stats.get(key);
if (value == null) {
value = stats.putIfAbsent(key, new AtomicLong(1));
}
if (value != null) {
value.incrementAndGet();
}
}
private Multiset<String> stats = ConcurrentHashMultiset.create();
public void notify ( String key )
{
stats.add(key, 1);
}
public class StatsService {
private ExecutorService executor = Executors.newSingleThreadExecutor();
private final Map<String,Long> stats = new HashMap<String,Long>();
public void notify(final String key) {
Runnable r = new Runnable() {
public void run() {
Long value = stats.get(key);
if (value == null) {
value = 1L;
} else {
value++;
}
stats.put(key, value);
// do the optional collectAndSave periodically
if (timeToDoCollectAndSave()) {
collectAndSave();
}
}
};
executor.execute(r);
}
}
public class StatsService {
private final Map<String, AtomicLong> stats = new HashMap<String, AtomicLong>(1000);
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
public void notify(final String key) {
r.lock();
AtomicLong count = stats.get(key);
if (count == null) {
r.unlock();
w.lock();
count = stats.get(key);
if(count == null) {
count = new AtomicLong();
stats.put(key, count);
}
r.lock();
w.unlock();
}
count.incrementAndGet();
r.unlock();
}
public Map<String, Long> getStats() {
w.lock();
Map<String, Long> copy = new HashMap<String, Long>();
for(Entry<String,AtomicLong> entry : stats.entrySet() ){
copy.put(entry.getKey(), entry.getValue().longValue());
}
stats.clear();
w.unlock();
return copy;
}
}
class Stats
{
public volatile long count;
}
class SomeRunnable implements Runnable
{
public void run()
{
doStuff();
stats.count++;
}
}
public long accumulateStats()
{
long count = previousCount;
for (Stats stat : allStats)
{
count += stat.count;
}
long resultDelta = count - previousCount;
previousCount = count;
return resultDelta;
}