我需要在我的软件中收集一些统计数据,并尝试使其快速和正确,这对我来说并不容易!
首先是我的代码,目前有两个类,一个是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并发实践,effective java等),但我强烈感觉需要您的建议来正确处理它。
我希望我表述的问题足够清晰简洁,以获得有价值的帮助。
编辑
感谢大家提供详细和有帮助的答案。正如我所预期的那样,有多种方法可以做到这一点。
我测试了大多数提议(我理解的那些)并上传了一个测试项目到Google Code以供进一步参考(Maven项目)
http://code.google.com/p/javastats/
我已经测试了不同的StatsService实现
- HashMapStatsService (HMSS)
- ConcurrentHashMapStatsService (CHMSS)
- LinkedQueueStatsService (LQSS)
- GoogleStatsService (GSS)
- ExecutorConcurrentHashMapStatsService (ECHMSS)
- ExecutorHashMapStatsService (EHMSS)
我使用x
个线程进行测试,每个线程调用notify方法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,因为它提供了良好的性能,同时也很容易理解。
感谢您的所有建议! Janning
StatsHarvester
中,没有必要同步run()
方法。 - matt bsynchronized
关键字(在CHMSS中)似乎是不必要的。除非您正在使用多个线程同时收集统计信息(显然,如果是这样,那么似乎很奇怪)。即使您在那里放置synchronized
,它也无法阻止线程调用notify()。如果是我,我就不会费心,因为统计数据不必“100%”准确。如果您使用CHM,则无法锁定整个映射。synchronized (stats) { copy = new HashMap(stats);
stats.clear();
}
return copy;
- Enno Shioji