我需要在我的软件中收集一些统计信息,并且我试图使其快速正确,这对我来说并不容易!
到目前为止,我的代码首先包含两个类,即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()
notify(key)
statsService.getStats()
所以我有很多作家,只有一个读者。拥有准确的统计信息会很好,但是我不在乎某些记录是否因高并发性而丢失。
读者应每5分钟或合理的时间运行一次。
写作应该尽可能快。读取速度应该很快,但是如果每5分钟锁定300ms左右,那就没问题了。
我已经阅读了许多文档(实际上是Java并发,有效的Java等),但是我有强烈的感觉,我需要您的建议才能正确解决问题。
我希望我说的问题清楚而简短,以期获得宝贵的帮助。
感谢所有您的详细和有用的答案。如我所料,有多种方法可以做到这一点。
我测试了您的大多数建议(我理解的那些建议),并将一个测试项目上传到Google代码以供进一步参考(Maven项目)
http://code.google.com/p/javastats/
我已经测试了StatsService的不同实现
我用x每个调用通知y时间的线程数测试了它们,结果以毫秒为单位
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,因为它提供了良好的性能,同时非常容易理解。
感谢您的输入!詹宁
正如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(); } }
这应该既快速又线程安全
编辑:重构,所以最多只有两个查找。