同步HashMap与ConcurrentHashMap写测试

时间:2015-02-25 13:42:34

标签: java java.util.concurrent

我学习了java.util.concurrency,我找到了一篇关于性能的文章(http://www.javamex.com/tutorials/concurrenthashmap_scalability.shtml)。我决定重复这些性能测试的一小部分用于研究目的。我已经为HashMapConcurrentHashMap编写了写测试。 我有两个问题:

  1. 是真的,为了获得最佳性能,我应该使用数字线程相等数量的CPU核心?
  2. 据我所知,性能因平台而异。但结果表明,HashMapConcurrentHashMap快一点。我认为应该是相同的,反之亦然。也许我在我的代码中犯了一个错误。
  3. 任何批评都是受欢迎的。

    package Concurrency;
    
    import java.util.concurrent.*;
    import java.util.*;
    
    class Writer2 implements Runnable {
        private Map<String, Integer> map;
        private static int index;
        private int nIteration; 
        private Random random = new Random();
        char[] chars = "abcdefghijklmnopqrstuvwxyz".toCharArray();
        private final CountDownLatch latch;
    
        public Writer2(Map<String, Integer> map, int nIteration, CountDownLatch latch) {
            this.map = map;
            this.nIteration = nIteration;
            this.latch = latch;
        }
    
        private synchronized String getNextString() {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 5; i++) {
                char c = chars[random.nextInt(chars.length)];
                sb.append(c);
            }
            sb.append(index);
            if(map.containsKey(sb.toString()))
                System.out.println("dublicate:" + sb.toString());
            return sb.toString();
        }
    
        private synchronized int getNextInt() { return index++; }
    
        @Override
        public void run() {
            while(nIteration-- > 0) {
                map.put(getNextString(), getNextInt());
            }
            latch.countDown();
        }
    }
    
    public class FourtyTwo {
        static final int nIteration = 100000;
        static final int nThreads = 4;
        static Long testMap(Map<String, Integer> map) throws InterruptedException{
            String name = map.getClass().getSimpleName(); 
            CountDownLatch latch = new CountDownLatch(nThreads);
            long startTime = System.currentTimeMillis();
                ExecutorService exec = Executors.newFixedThreadPool(nThreads);
                for(int i = 0; i < nThreads; i++)
                    exec.submit(new Writer2(map, nIteration, latch));
                latch.await();  
                exec.shutdown();
            long endTime = System.currentTimeMillis();
            System.out.format(name + ": that took %,d milliseconds %n", (endTime - startTime));
            return (endTime - startTime);
        }
        public static void main(String[] args) throws InterruptedException {
            ArrayList<Long> result = new ArrayList<Long>() {
                @Override
                public String toString() {
                    Long result = 0L;
                    Long size = new Long(this.size());
                    for(Long i : this)
                        result += i;
                    return String.valueOf(result/size);
                }
            }; 
    
            Map<String, Integer> map1 = Collections.synchronizedMap(new HashMap<String, Integer>());
            Map<String, Integer> map2 = new ConcurrentHashMap<>();
    
            System.out.println("Rinning test...");
            for(int i = 0; i < 5; i++) {
                //result.add(testMap(map1)); 
                result.add(testMap(map2));
            }
            System.out.println("Average time:" + result + " milliseconds");
    
        }
    
    }
    
    /*
    OUTPUT:
    ConcurrentHashMap: that took 5 727 milliseconds 
    ConcurrentHashMap: that took 2 349 milliseconds 
    ConcurrentHashMap: that took 9 530 milliseconds 
    ConcurrentHashMap: that took 25 931 milliseconds 
    ConcurrentHashMap: that took 1 056 milliseconds 
    Average time:8918 milliseconds
    
    SynchronizedMap: that took 6 471 milliseconds 
    SynchronizedMap: that took 2 444 milliseconds 
    SynchronizedMap: that took 9 678 milliseconds 
    SynchronizedMap: that took 10 270 milliseconds 
    SynchronizedMap: that took 7 206 milliseconds 
    Average time:7213 milliseconds
    
    */
    

2 个答案:

答案 0 :(得分:1)


有多少线程不同,不是CPU,而是你正在做的事情。例如,如果您对线程执行的操作是高度磁盘密集型的,那么您的CPU可能不会被最大化,因此执行8个线程可能只会导致严重的颠簸。但是,如果你有大量的磁盘活动,接着是繁重的计算,然后是更多的磁盘活动,你可以从错开线程,拆分活动和分组,以及使用更多线程中受益。例如,在这种情况下,您可能希望将使用单个文件的文件活动组合在一起,但可能不是您从一堆文件中提取的活动(除非它们是在磁盘上连续写入的)。当然,如果你过度思考磁盘IO,你可能会严重损害你的性能,但我要说的是你不应该只是推卸它。在这样的程序中,我可能会有专用于磁盘IO的线程,专用于CPU工作的线程。分而治之。您拥有更少的IO线程和更多的CPU线程。

同步服务器运行比核心/ CPU更多的线程是很常见的,因为大多数线程只能在短时间内工作,或者不会进行大量CPU密集型工作。但是,拥有500个线程没有用,如果你只有2个客户端,那些多余线程的上下文切换会妨碍性能。这是一种平衡行为,通常需要进行一些调整。

简而言之

  1. 想想你做什么
    • 网络活动很少,所以线程通常都很好
    • 如果你的线程比核心多2倍,那么CPU密集型的东西并没有太大的好处...通常略高于1x或略低于1x是最佳的,但你必须测试,测试,测试< / LI>
    • 拥有10个磁盘IO密集型线程可能会损害所有10个线程,就像拥有30个CPU密集型线程一样......颠簸会伤害所有线程
  2. 尽量分散痛苦
    • 看看它是否有助于分散CPU,IO等工作,或者群集是否更好......这取决于你在做什么
  3. 尝试分组
    • 如果可以,请分离您的磁盘,IO和网络任务,并为他们提供自己的线程,这些线程可以调整到这些任务

  4. 两个

    通常,线程不安全的方法运行得更快。类似地,使用本地化同步比同步整个方法运行得更快。因此,HashMap通常比ConcurrentHashMap快得多。与StringBuilder相比,另一个例子是StringBuffer。 StringBuffer是同步的,不仅速度慢,而且同步更重(更多代码等);它应该很少使用。但是,如果有多个线程命中它,则StringBuilder 不安全。话虽如此,StringBuffer和ConcurrentHashMap也可以竞争。 “线程安全”并不意味着您可以毫无思考地使用它,特别是这两个类的操作方式。例如,如果您同时进行读写操作(例如,在执行put或remove时使用contains(Object)),您仍然可以处于竞争状态。如果你想阻止这样的事情,你必须使用自己的类或同步你的ConcurrentHashMap调用。

    我通常使用非并发映射和集合,只使用我自己的锁我需要它们。你会发现它的速度要快得多,控制也很棒。 Atomics(例如AtomicInteger)有时很好,但对我所做的事情通常并不常用。玩类,玩同步,你会发现你可以比ConcurrentHashMap,StringBuffer等的霰弹枪方法更有效地掌握你。如果你不这样做,你可以有竞争条件,无论你是否使用这些类它是对的...但如果你自己做,你也可以更高效,更小心。


    实施例

    请注意,我们有一个新的Object,我们正在锁定它。在方法上使用此代替synchronized

    public final class Fun {
        private final Object lock = new Object();
    
        /*
         * (non-Javadoc)
         *
         * @see java.util.Map#clear()
         */
        @Override
        public void clear() {
            // Doing things...
            synchronized (this.lock) {
                // Where we do sensitive work
            }
        }
    
        /*
         * (non-Javadoc)
         *
         * @see java.util.Map#put(java.lang.Object, java.lang.Object)
         */
        @Override
        public V put(final K key, @Nullable final V value) {
            // Doing things...
            synchronized (this.lock) {
                // Where we do sensitive work
            }
            // Doing things...
        }
    }
    

    从你的代码......

    我可能不会将sb.append(index)放在锁中,或者可能有一个单独的锁用于索引调用,但是......

        private final Object lock = new Object();
    
        private String getNextString() {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 5; i++) {
                char c = chars[random.nextInt(chars.length)];
                sb.append(c);
            }
            synchronized (lock) {
                sb.append(index);
                if (map.containsKey(sb.toString()))
                    System.out.println("dublicate:" + sb.toString());
            }
            return sb.toString();
        }
    
        private int getNextInt() {
            synchronized (lock) {
                return index++;
            }
        }
    

答案 1 :(得分:0)

您链接的文章并未声明ConcurrentHashMap通常比同步HashMap更快,只是它可以更好地扩展;即对于大量线程来说它更快。正如您在图表中看到的,对于4个线程,性能非常相似。

除此之外,您应该测试更多项目,正如我的结果所示,它们可能会有很大不同:

Rinning test...
SynchronizedMap: that took 13,690 milliseconds 
SynchronizedMap: that took 8,210 milliseconds 
SynchronizedMap: that took 11,598 milliseconds 
SynchronizedMap: that took 9,509 milliseconds 
SynchronizedMap: that took 6,992 milliseconds 
Average time:9999 milliseconds

Rinning test...
ConcurrentHashMap: that took 10,728 milliseconds 
ConcurrentHashMap: that took 7,227 milliseconds 
ConcurrentHashMap: that took 6,668 milliseconds 
ConcurrentHashMap: that took 7,071 milliseconds 
ConcurrentHashMap: that took 7,320 milliseconds 
Average time:7802 milliseconds

请注意,您的代码并未清除循环之间的Map,每次添加nIteration更多项目......是您想要的吗?

你的getNextInt / getNextString不需要

synchronized,因为它们不是从多个线程调用的。