以线程安全的方式更新ConcurrentHashMap

时间:2013-04-27 23:55:26

标签: java multithreading thread-safety concurrenthashmap

我正在对客户端代码中的几个方法进行基准测试,以了解这些方法花费了多少时间。所以我编写了一个多线程程序,它将生成多个线程,然后我将测量这些方法在客户端代码和服务器端代码中花费的时间。

我将ConcurrentHashMap声明为

public static ConcurrentHashMap<Long, Long> map = new ConcurrentHashMap<Long, Long>();

现在我试图找出,在X毫秒内回拨了多少电话,所以我将这些数字存储在上面的地图中。所以上面的地图会存储这样的东西 -

KEY will be Milliseconds
and VALUE will be Number of calls came back in those milliseconds.

下面是代码,我有。

final long start = System.nanoTime();

method.getInformation();

final long end = System.nanoTime() - start;
final long key = end / 1000000L;
boolean done = false;
while(!done) {
    Long oldValue = map.putIfAbsent(key, 1L);
    if(oldValue != null) {
        done = map.replace(key, oldValue, oldValue + 1);
    } else {
        done = true;
    }
}

我正在尝试查看上述代码是否有任何问题?

为什么我要问的是,如果我正在运行Multithreading program,那么server CPU usage通常会转到80-90%左右。但是,如果我从服务器端代码中删除上述基准测试代码,则CPU usage不会转到80-90%。所以这就是原因,我试图看看是否有更好的方法来编写上述基准测试代码,可以实现相同的上述场景?

感谢您的帮助。

更新: -

我使用unix中的TOP命令监视服务器端的CPU和MEMORY使用情况 -

top - 17:13:18 up 25 days, 23:24,  4 users,  load average: 1.72, 1.43, 1.04
Tasks: 114 total,   1 running, 111 sleeping,   0 stopped,   2 zombie
Cpu0  : 79.2%us,  5.8%sy,  0.0%ni, 23.1%id,  0.0%wa,  0.0%hi,  1.9%si,  0.0%st
Cpu1  : 83.7%us,  3.7%sy,  0.0%ni, 40.7%id,  0.0%wa,  0.0%hi,  1.9%si,  0.0%st
Mem:   6127684k total,  5122736k used,  1004948k free,   240436k buffers
Swap:  1331196k total,        0k used,  1331196k free,  2485984k cached

下面是正在运行的快照,我刚捕获了

3 个答案:

答案 0 :(得分:0)

所以我用你的时间检查代码进行了快速测试,并且能够在我的四核2013 Macbook Pro上以8.8秒的速度运行1 百万。这意味着每个通话费用不到8ns(因为我的时间是挂钟并且考虑了线程的东西)。这是相当便宜的IMO。我将0到1000000的随机值保持在100个线程中,最终得到的堆大小为631k,这似乎占用了不到20-30mb的核心。

在我看来,您的通话时间代码不是一个很大的性能问题。我想知道每个线程是否都触及大量内存,而你的代码所做的唯一事情就是遇到内存障碍。可能有趣的是用volatile字段的单个更新替换代码或强制屏障,看看你是否得到相同的行为。


我没有立即发现您的代码存在任何问题,我也不会认为这会是一个很大的性能问题。

我确实想知道地图有多大。似乎是如果你在地图中使用大量条目长时间运行它,内存需求将会很大。您可以尝试使用-Xmx参数来增加内存。

如果是内存,那么CPU问题可能与GC有关。我使用Jconsole查看你是否内存不足。

答案 1 :(得分:0)

这里有一些潜在的性能问题。

  1. 您的基准测试代码为每个请求添加了两次System.nanoTime()调用。根据请求中涉及的真实工作量,这些调用可能很重要。

  2. 虽然您尝试使用ConcurrentHashMap来减少并发性瓶颈,但这并不能完全消除它。实际上,如果您有大量请求需要相同的毫秒数,那么该特定计数器就会出现瓶颈。如果特定计数器存在争用,那么当不同的线程竞争更新它时,这将导致“旋转”。

  3. 如果(假设)地图变得非常大,您可以开始获取与内存使用相关的问题;例如更大的工作集,更高的缓存和TLB争用,颠簸。

  4. 但最重要的是,您添加的任何性能测量代码都可能会改变系统的性能特征。

答案 2 :(得分:0)

性能/资源使用问题:

  • 在每次调用期间读取和写入HashMap可能很昂贵,而并发散列映射更是如此。这不是必需的。
  • 您也可以调用System.nanoTime(),这可能很昂贵,并且您在每次迭代时都会执行此操作。这不是必需的。

我的建议

  • 在执行方法的次数上声明静态运行计数器
  • 在同步块中,递增计数器
  • 当计数器达到阈值(例如1000)时,然后确定以毫秒为单位的经过时间
  • 记录列表中的已用时间

    static List<long> methodElapsedMillisList = new ArrayList<long>();
    final int methodCountBlock = 1000; 
    static long methodCountStartMillis = System.currentTimeMillis();
    static int methodCount = 0;
    static Object methodCountMonitorObj = new Object();
    
    
    // within instrumented method:
    synchronised (methodCountMonitorObj) {
           methodCount++;
           if (methodCount > methodCountBlock ) {
                long newMethodCountStartMillis = System.currentTimeMillis();
                long elapsedMillis = newMethodCountStartMillis - methodCountStartMillis;
                methodCountStartMillis = newMethodCounterStartMillis;
                methodElapsedMillisList.add(elapsedMillis);
                methodCount = 0;
           }
    }