一尘不染

java并发性:很多作家,一个读者

java

我需要在我的软件中收集一些统计信息,并且我试图使其快速正确,这对我来说并不容易!

到目前为止,我的代码首先包含两个类,即StatsService和StatsHarvester

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;
}
}

这是我的第二堂课,一个收割机,它不时收集统计数据并将其写入数据库。

public class StatsHarvester implements Runnable
{
private StatsService    statsService;
private Thread          t;

public void init ( )
{
    t = new Thread(this);
    t.start();
}

public synchronized void run ( )
{
    while (true)
    {
        try
        {
            wait(5 * 60 * 1000); // 5 minutes
            collectAndSave();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

private void collectAndSave ( )
{
    Map<String, Long> stats = statsService.getStats();
    // do something like:
    // saveRecords(stats);
}
}

在运行时,它将有大约30个并发运行的线程,每个线程调用notify(key)约100次。只有一个StatsHarvester正在呼叫statsService.getStats()

所以我有很多作家,只有一个读者。拥有准确的统计信息会很好,但是我不在乎某些记录是否因高并发性而丢失。

读者应每5分钟或合理的时间运行一次。

写作应该尽可能快。读取速度应该很快,但是如果每5分钟锁定300ms左右,那就没问题了。

我已经阅读了许多文档(实际上是Java并发,有效的Java等),但是我有强烈的感觉,我需要您的建议才能正确解决问题。

我希望我说的问题清楚而简短,以期获得宝贵的帮助。


编辑

感谢所有您的详细和有用的答案。如我所料,有多种方法可以做到这一点。

我测试了您的大多数建议(我理解的那些建议),并将一个测试项目上传到Google代码以供进一步参考(Maven项目)

http://code.google.com/p/javastats/

我已经测试了StatsService的不同实现

  • HashMapStatsService(HMSS)
  • ConcurrentHashMapStatsService(CHMSS)
  • LinkedQueueStatsService(LQSS)
  • GoogleStatsService(GSS)
  • ExecutorConcurrentHashMapStatsService(ECHMSS)
  • ExecutorHashMapStatsService(EHMSS)

我用x每个调用通知y时间的线程数测试了它们,结果以毫秒为单位

         10,100   10,1000  10,5000  50,100   50,1000  50,5000  100,100  100,1000 100,5000 
GSS       1        5        17       7        21       117      7        37       254       Summe: 466
ECHMSS    1        6        21       5        32       132      8        54       249       Summe: 508
HMSS      1        8        45       8        52       233      11       103      449       Summe: 910
EHMSS     1        5        24       7        31       113      8        67       235       Summe: 491
CHMSS     1        2        9        3        11       40       7        26       72        Summe: 171
LQSS      0        3        11       3        16       56       6        27       144       Summe: 266

目前,我认为我将使用ConcurrentHashMap,因为它提供了良好的性能,同时非常容易理解。

感谢您的输入!詹宁


阅读 177

收藏
2020-12-03

共1个答案

一尘不染

正如jack所不及的,您可以使用java.util.concurrent库,该库包含ConcurrentHashMap和AtomicLong。您可以将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();
    }
}

这应该既快速又线程安全

编辑:重构,所以最多只有两个查找。

2020-12-03