java - 同步HashMap与ConcurrentHashMap编写测试

标签 java java.util.concurrent

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

  • 是的,为了获得最佳性能,我应该使用数量等于CPU核心数的线程?
  • 我了解,平台之间的性能会有所不同。但是结果表明HashMapConcurrentHashMap快一些。我认为应该是相同的,反之亦然。也许我在代码中犯了一个错误。

  • 任何批评都值得欢迎。
    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
    
    */
    

    最佳答案

    之一

    有多少个线程变化,而不是取决于CPU,而是取决于您正在做什么。例如,如果您对线程进行的操作占用大量磁盘空间,则您的CPU可能无法达到极限,因此执行8个线程可能只会导致严重的崩溃。但是,如果您有大量的磁盘 Activity ,接着是繁重的计算,然后是更多的磁盘 Activity ,那么您将受益于错开线程,拆分 Activity 并将其分组以及使用更多线程。例如,在这种情况下,您可能希望将使用单个文件的文件 Activity 分组在一起,但如果不是从一堆文件中提取文件的 Activity ,则可能不希望分组(除非它们连续写入磁盘中)。当然,如果您过分考虑磁盘IO,则可能会严重损害性能,但是我要说的是,您也不应该仅仅推卸它。在这样的程序中,我可能会有专用于磁盘IO的线程,专用于CPU工作的线程。分而治之。您将拥有更少的IO线程和更多的CPU线程。

    同步服务器运行的线程多于内核/ CPU,这是很常见的,因为这些线程中的大多数要么仅在很短的时间内工作,要么就不需要大量的CPU密集型工作。但是,如果您只有2个客户端,并且这些多余线程的上下文切换会影响性能,那么拥有500个线程并没有多大用处。这是一种平衡行为,通常需要进行一些微调。

    简而言之

  • 想想你在做什么
  • 网络 Activity 较少,因此更多线程通常是好的
  • 如果占用的线程数比内核多2倍,那么CPU密集型的事情就没有太大用处了……通常最好多于1倍或少于1倍,但是您必须测试,测试,测试
  • 拥有10个磁盘IO密集线程可能会损害所有10个线程,就像拥有30个CPU密集线程...崩溃会伤害它们所有
  • 尝试消除痛苦
  • 看看是否有助于分散CPU,IO等工作,或者集群是否更好...这取决于您在做什么
  • 尝试将事情分组
  • 如果可以,请分离磁盘,IO和网络任务,并为它们分配自己的线程,这些线程将被调整为这些任务




  • 通常,线程不安全的方法运行速度更快。同样,使用本地化同步比同步整个方法运行得更快。因此,HashMap通常比ConcurrentHashMap快得多。与StringBuilder相比,另一个示例是StringBuffer。 StringBuffer已同步,不仅速度较慢,而且同步更重(更多代码等);它应该很少使用。但是,如果有多个线程命中,则StringBuilder是不安全的。话虽如此,StringBuffer和ConcurrentHashMap也可以竞争。 “线程安全”并不意味着您可以不加思索地使用它,尤其是这两个类的运行方式。例如,如果您同时进行读写操作(例如,在放置或移除时使用contains(Object)),那么您仍然可以处于竞争状态。如果要防止此类情况,则必须使用自己的类或将调用同步到ConcurrentHashMap。

    我通常使用非并发的映射和集合,只在需要它们的地方使用我自己的锁。您会发现这种方式要快得多,而且控件很棒。原子(例如AtomicInteger)有时很不错,但实际上对我所做的事情通常没有用。玩这些类,玩同步,您会发现自己比ConcurrentHashMap,StringBuffer等的散弹枪方法更有效地掌握。如果您不使用这些类,则可能存在竞争条件没错...但是如果您自己动手,还可以更加高效,更加谨慎。



    请注意,我们有一个锁定的新对象。在方法上使用它代替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++;
            }
        }
    

    关于java - 同步HashMap与ConcurrentHashMap编写测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28720558/

    相关文章:

    java - 如果其他工作,则不切换

    java - Java 中的参数化良构和捕获转换

    java - 如何在警报对话框上显示用户输入详细信息?

    java - 在java中用空格分割字符串数据

    Java ExecutionrCompletionService take.get() 从不同的线程

    java ConcurrentHashMap of Integer vs HashMap of AtomicInteger vs ConcurrentHashMap of AtomicInteger

    java - 线程池,worker既是生产者又是消费者

    java - 使用 CompletionService 时强制执行 executorService.awaitTermination

    java - 获取在设定时间内完成的所有 future ?

    java - 使用java中的线程进行文件的多个副本