zoukankan      html  css  js  c++  java
  • 有关如何线程安全的使用map(hashMap)

    最近在写一个多线程中控制输出顺序的系统中的一个代码,使用了map的数据结构。具体的业务是需要一个单例的对象,然后需要在多线程的环境下实现添加和删除的操作。部分代码如下:

    public class UploadImageNumCache {
    
        /**
         * 
        
        private Map<Integer, Map<Integer, Integer>> UploadImageNumMap = Collections
                .synchronizedMap(new HashMap<Integer, Map<Integer, Integer>>());
         */
        private Map<Integer, Map<Integer, Integer>> UploadImageNumMap = new ConcurrentHashMap<Integer, Map<Integer,Integer>>();
    
        /**
         * 
         */
        private static UploadImageNumCache uploadImageNumCache = null;
    
        /**
         * 私有构造
         */
        private UploadImageNumCache() {
    
        }
    
        /**
         * 添加
         * 
         * @param documentId
         *            文档id
         * @param pageNow
         *            页码
         */
        public synchronized void addUploadImageNumMap(Integer documentId, Integer pageNow) {
    
            if (UploadImageNumMap.containsKey(documentId)) {
                UploadImageNumMap.get(documentId).put(pageNow, Constants.IMAGE_UPLOAD_STATUS_NO);
            } else {
                Map<Integer, Integer> map = new HashMap<Integer, Integer>();
                map.put(pageNow, Constants.IMAGE_UPLOAD_STATUS_NO);
                UploadImageNumMap.put(documentId, map);
            }
        }
    /**
         * 删除
         * 
         * @param documentId
         */
        public synchronized void deleteUploadImageNumMap(Integer documentId) {
            if (UploadImageNumMap.containsKey(documentId)) {
                UploadImageNumMap.remove(documentId);
            }
        }
    /**
         * 清除缓存
         */
        public synchronized void clearUploadImageNumMap() {
            if (!UploadImageNumMap.isEmpty()) {
                UploadImageNumMap.clear();
            }
        }
    
        /**
         * 获取单例
         * 
         * @return
         */
        public static UploadImageNumCache getInstance() {
            if (uploadImageNumCache == null) {
                synchronized (UploadImageNumCache.class) {
                    if (uploadImageNumCache == null) {
                        uploadImageNumCache = new UploadImageNumCache();
                    }
                }
            }
            return uploadImageNumCache;
        }

    从上面的代码中可以看到使用了map的数据结构来存放。但是在这里是修改过的代码。之前直接使用了hashmap。但是遇到一个很严重的问题就是多线程环境下的线程安全问题。我们都知道map,hashmap不是线程安全的。记得之前的面试的时候问过list如何实现线程安全,当时没有答上来,出来后就百度了以下,知道是使用的Collections .synchronizedList。但是写map的时候竟然没有想起来。
    实在是惭愧阿。今天就对这些涉及到的集合中的线程安全问题进行一个总结,多总结多进步阿。

    首先说以下 java中集合的两种分类。底层来说的话分两类collection和map:

    Collection
    ├List
    │├LinkedList
    │├ArrayList
    │└Vector
    │ └Stack
    └Set
    Map
    ├Hashtable
    ├HashMap
    └WeakHashMap。这个图比较详细的说明了。

    我们说集合中有些是线程安全的有例如:Vector,HashTable等。这些类之所以是线程安全的是因为,这些类是在jdk1.5之前,甚至是1.2版本的,我们看这些类的源码就可以知道里面都有sychronized这个线程安全关键字。但是之后出的ArrayList,HashMap等,一般都是线程不安全的。也不知道是基于什么考虑的,这个有时间可以研究以下。今天主要对hashmap和list的线程安全实现做一个介绍,至于hashtable这个线程安全和hashmap的区别不是今天要说的内容。

    好了既然我们知道map,hashmap不是线程安全的,但是如何证明呢,下面的这个程序大家可以自己试一下,看看能不能将到5000 正确的输出来。:

    /**
     * 
     * @author duanxj
     *
     * @version 
     *
     * @date May 8, 2017
     */
    public class ThreadNotSafeHashmap {
        public static void main(String args[]) throws InterruptedException {
            final HashMap<String, String> firstHashMap = new HashMap<String, String>();
            Thread t1 = new Thread() {
                public void run() {
                    for (int i = 0; i < 2500; i++) {
                        firstHashMap.put(String.valueOf(i), String.valueOf(i));
                    }
                }
            };
            Thread t2 = new Thread() {
                public void run() {
                    for (int j = 2500; j < 5000; j++) {
                        firstHashMap.put(String.valueOf(j), String.valueOf(j));
                    }
                }
            };
            t1.start();
           t2.start();
    
            Thread.sleep(1000);
            for (int k = 0; k < 5000; k++) {
                if (String.valueOf(k).equals(firstHashMap.get(String.valueOf(k)))) {
                    System.err.println(String.valueOf(k) + ":" + firstHashMap.get(String.valueOf(k)));
                }
            }
        }
    }

    而且你要多试几次,你会发现每次跟每次少的元素都不一样。这下明白为什么不是线程安全的了吧。下面说到这里未还想说以下,有些人说多线程对hashmap进行添加和删除的时候会抛出异常。这种说法是不准确的,虽然我们知道在对list进行遍历的时候不能对list做删除操作,会抛出异常,但是在map中并不会抛出同样的异常。至于为什么大家百度以下。

    上面是一个证明map线程不安全的例子,既然是线程不安全的,那总得知道为什么把:

    总说HashMap是线程不安全的,不安全的,不安全的,那么到底为什么它是线程不安全的呢?要回答这个问题就要先来简单了解一下HashMap源码中的使用的存储结构(这里引用的是Java 8的源码,与7是不一样的)和它的扩容机制

    HashMap的内部存储结构

    下面是HashMap使用的存储结构:

    1
    2
    3
    4
    5
    6
    7
    8
    transient Node<K,V>[] table;
     
    static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;
            final K key;
            V value;
            Node<K,V> next;
    }

    可以看到HashMap内部存储使用了一个Node数组(默认大小是16),而Node类包含一个类型为Node的next的变量,也就是相当于一个链表,所有hash值相同(即产生了冲突)的key会存储到同一个链表里,这是他底层的存储结构,那从这个结构中我们分析为什么是线程不安全的呢?

           个人觉得HashMap在并发时可能出现的问题主要是两方面,首先如果多个线程同时使用put方法添加元素,而且假设正好存在两个put的key发 生了碰撞(hash值一样),那么根据HashMap的实现,这两个key会添加到数组的同一个位置,这样最终就会发生其中一个线程的put的数据被覆 盖。第二就是如果多个线程同时检测到元素个数超过数组大小*loadFactor,这样就会发生多个线程同时对Node数组进行扩容,都在重新计算元素位 置以及复制数据,但是最终只有一个线程扩容后的数组会赋给table,也就是说其他线程的都会丢失,并且各自线程put的数据也丢失。
    关于HashMap线程不安全这一点,《Java并发编程的艺术》一书中是这样说的:

    HashMap在并发执行put操作时会引起死循环,导致CPU利用率接近100%。因为多线程会导致HashMap的Node链表形成环形数据结构,一旦形成环形数据结构,Node的next节点永远不为空,就会在获取Node时产生死循环。

    哇塞,听上去si不si好神奇,居然会产生死循环。。。。google了一下,才知道死循环并不是发生在put操作时,而是发生在扩容时。详细的解释可以看下面几篇博客:

    既然知道了为什么,那就要去解决,如何解决呢,到目前为止有下面三种解决方法:

    • Hashtable   ConcurrentHashMap   Synchronized Map
    • 下面按照这个顺序对每一个进行说明。顺便说一下他们的效率问题:
    • 例子:

    • //Hashtable
      Map<String, String> hashtable = new Hashtable<>();
       
      //synchronizedMap
      Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());
       
      //ConcurrentHashMap
      Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
       

      依次来看看。

      Hashtable

      先稍微吐槽一下,为啥命名不是HashTable啊,看着好难受,不管了就装作它叫HashTable吧。这货已经不常用了,就简单说说吧。HashTable源码中是使用synchronized来保证线程安全的,比如下面的get方法和put方法:

      1
      2
      3
      4
      5
      6
      public synchronized V get(Object key) {
             // 省略实现
          }
      public synchronized V put(K key, V value) {
          // 省略实现
          }

      所以当一个线程访问HashTable的同步方法时,其他线程如果也要访问同步方法,会被阻塞住。举个例子,当一个线程使用put方法时,另一个线程不但不可以使用put方法,连get方法都不可以,好霸道啊!!!so~~,效率很低,现在基本不会选择它了。

      ConcurrentHashMap

      ConcurrentHashMap(以下简称CHM)是JUC包中的一个类,Spring的源码中有很多使用CHM的地方。之前已经翻译过一篇关于ConcurrentHashMap的博客,如何在java中使用ConcurrentHashMap, 里面介绍了CHM在Java中的实现,CHM的一些重要特性和什么情况下应该使用CHM。需要注意的是,上面博客是基于Java 7的,和8有区别,在8中CHM摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法,有时间会重新总结一下。

      SynchronizedMap

      看了一下源码,SynchronizedMap的实现还是很简单的。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      // synchronizedMap方法
      public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
             return new SynchronizedMap<>(m);
         }
      // SynchronizedMap类
      private static class SynchronizedMap<K,V>
             implements Map<K,V>, Serializable {
             private static final long serialVersionUID = 1978198479659022715L;
       
             private final Map<K,V> m;     // Backing Map
             final Object      mutex;        // Object on which to synchronize
       
             SynchronizedMap(Map<K,V> m) {
                 this.m = Objects.requireNonNull(m);
                 mutex = this;
             }
       
             SynchronizedMap(Map<K,V> m, Object mutex) {
                 this.m = m;
                 this.mutex = mutex;
             }
       
             public int size() {
                 synchronized (mutex) {return m.size();}
             }
             public boolean isEmpty() {
                 synchronized (mutex) {return m.isEmpty();}
             }
             public boolean containsKey(Object key) {
                 synchronized (mutex) {return m.containsKey(key);}
             }
             public boolean containsValue(Object value) {
                 synchronized (mutex) {return m.containsValue(value);}
             }
             public V get(Object key) {
                 synchronized (mutex) {return m.get(key);}
             }
       
             public V put(K key, V value) {
                 synchronized (mutex) {return m.put(key, value);}
             }
             public V remove(Object key) {
                 synchronized (mutex) {return m.remove(key);}
             }
             // 省略其他方法
         }

      从源码中可以看出调用synchronizedMap()方法后会返回一个SynchronizedMap类的对象,而在SynchronizedMap类中使用了synchronized同步关键字来保证对Map的操作是线程安全的。

      性能对比

      这是要靠数据说话的时代,所以不能只靠嘴说CHM快,它就快了。写个测试用例,实际的比较一下这三种方式的效率(源码来源),下面的代码分别通过三种方式创建Map对象,使用ExecutorService来并发运行5个线程,每个线程添加/获取500K个元素。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      public class CrunchifyConcurrentHashMapVsSynchronizedMap {
       
          public final static int THREAD_POOL_SIZE = 5;
       
          public static Map<String, Integer> crunchifyHashTableObject = null;
          public static Map<String, Integer> crunchifySynchronizedMapObject = null;
          public static Map<String, Integer> crunchifyConcurrentHashMapObject = null;
       
          public static void main(String[] args) throws InterruptedException {
       
              // Test with Hashtable Object
              crunchifyHashTableObject = new Hashtable<>();
              crunchifyPerformTest(crunchifyHashTableObject);
       
              // Test with synchronizedMap Object
              crunchifySynchronizedMapObject = Collections.synchronizedMap(new HashMap<String, Integer>());
              crunchifyPerformTest(crunchifySynchronizedMapObject);
       
              // Test with ConcurrentHashMap Object
              crunchifyConcurrentHashMapObject = new ConcurrentHashMap<>();
              crunchifyPerformTest(crunchifyConcurrentHashMapObject);
       
          }
       
          public static void crunchifyPerformTest(final Map<String, Integer> crunchifyThreads) throws InterruptedException {
       
              System.out.println("Test started for: " + crunchifyThreads.getClass());
              long averageTime = 0;
              for (int i = 0; i < 5; i++) {
       
                  long startTime = System.nanoTime();
                  ExecutorService crunchifyExServer = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
       
                  for (int j = 0; j < THREAD_POOL_SIZE; j++) {
                      crunchifyExServer.execute(new Runnable() {
                          @SuppressWarnings("unused")
                          @Override
                          public void run() {
       
                              for (int i = 0; i < 500000; i++) {
                                  Integer crunchifyRandomNumber = (int) Math.ceil(Math.random() * 550000);
       
                                  // Retrieve value. We are not using it anywhere
                                  Integer crunchifyValue = crunchifyThreads.get(String.valueOf(crunchifyRandomNumber));
       
                                  // Put value
                                  crunchifyThreads.put(String.valueOf(crunchifyRandomNumber), crunchifyRandomNumber);
                              }
                          }
                      });
                  }
       
                  // Make sure executor stops
                  crunchifyExServer.shutdown();
       
                  // Blocks until all tasks have completed execution after a shutdown request
                  crunchifyExServer.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
       
                  long entTime = System.nanoTime();
                  long totalTime = (entTime - startTime) / 1000000L;
                  averageTime += totalTime;
                  System.out.println("2500K entried added/retrieved in " + totalTime + " ms");
              }
              System.out.println("For " + crunchifyThreads.getClass() + " the average time is " + averageTime / 5 + " ms
      ");
          }
      }

      测试结果:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      Test started for: class java.util.Hashtable
      2500K entried added/retrieved in 2018 ms
      2500K entried added/retrieved in 1746 ms
      2500K entried added/retrieved in 1806 ms
      2500K entried added/retrieved in 1801 ms
      2500K entried added/retrieved in 1804 ms
      For class java.util.Hashtable the average time is 1835 ms
       
      Test started for: class java.util.Collections$SynchronizedMap
      2500K entried added/retrieved in 3041 ms
      2500K entried added/retrieved in 1690 ms
      2500K entried added/retrieved in 1740 ms
      2500K entried added/retrieved in 1649 ms
      2500K entried added/retrieved in 1696 ms
      For class java.util.Collections$SynchronizedMap the average time is 1963 ms
       
      Test started for: class java.util.concurrent.ConcurrentHashMap
      2500K entried added/retrieved in 738 ms
      2500K entried added/retrieved in 696 ms
      2500K entried added/retrieved in 548 ms
      2500K entried added/retrieved in 1447 ms
      2500K entried added/retrieved in 531 ms
      For class java.util.concurrent.ConcurrentHashMap the average time is 792 ms
      这个就不用废话了,CHM性能是明显优于Hashtable和SynchronizedMap的,CHM花费的时间比前两个的一半还少.
    • 哈哈哈  上面的分析是参考的这个兄弟的。www.importnew.com/21396.html 觉着写的很好,权当未参考参考。通过上面的分析我们看到其实建议使用

      ConcurrentHashMap来实现map的线程安全问题。

    • 对于list如何显示线程安全,其实使用的也是collections包中的Collections.synchronizedList(new ArrayList<Map<String,Object>>());

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    //Hashtable
    Map<String, String> hashtable = new Hashtable<>();
     
    //synchronizedMap
    Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());
     
    //ConcurrentHashMap
    Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();

    依次来看看。

    Hashtable

    先稍微吐槽一下,为啥命名不是HashTable啊,看着好难受,不管了就装作它叫HashTable吧。这货已经不常用了,就简单说说吧。HashTable源码中是使用synchronized来保证线程安全的,比如下面的get方法和put方法:

    1
    2
    3
    4
    5
    6
    public synchronized V get(Object key) {
           // 省略实现
        }
    public synchronized V put(K key, V value) {
        // 省略实现
        }

    所以当一个线程访问HashTable的同步方法时,其他线程如果也要访问同步方法,会被阻塞住。举个例子,当一个线程使用put方法时,另一个线程不但不可以使用put方法,连get方法都不可以,好霸道啊!!!so~~,效率很低,现在基本不会选择它了。

    ConcurrentHashMap

    ConcurrentHashMap(以下简称CHM)是JUC包中的一个类,Spring的源码中有很多使用CHM的地方。之前已经翻译过一篇关于ConcurrentHashMap的博客,如何在java中使用ConcurrentHashMap, 里面介绍了CHM在Java中的实现,CHM的一些重要特性和什么情况下应该使用CHM。需要注意的是,上面博客是基于Java 7的,和8有区别,在8中CHM摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法,有时间会重新总结一下。

    SynchronizedMap

    看了一下源码,SynchronizedMap的实现还是很简单的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    // synchronizedMap方法
    public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
           return new SynchronizedMap<>(m);
       }
    // SynchronizedMap类
    private static class SynchronizedMap<K,V>
           implements Map<K,V>, Serializable {
           private static final long serialVersionUID = 1978198479659022715L;
     
           private final Map<K,V> m;     // Backing Map
           final Object      mutex;        // Object on which to synchronize
     
           SynchronizedMap(Map<K,V> m) {
               this.m = Objects.requireNonNull(m);
               mutex = this;
           }
     
           SynchronizedMap(Map<K,V> m, Object mutex) {
               this.m = m;
               this.mutex = mutex;
           }
     
           public int size() {
               synchronized (mutex) {return m.size();}
           }
           public boolean isEmpty() {
               synchronized (mutex) {return m.isEmpty();}
           }
           public boolean containsKey(Object key) {
               synchronized (mutex) {return m.containsKey(key);}
           }
           public boolean containsValue(Object value) {
               synchronized (mutex) {return m.containsValue(value);}
           }
           public V get(Object key) {
               synchronized (mutex) {return m.get(key);}
           }
     
           public V put(K key, V value) {
               synchronized (mutex) {return m.put(key, value);}
           }
           public V remove(Object key) {
               synchronized (mutex) {return m.remove(key);}
           }
           // 省略其他方法
       }

    从源码中可以看出调用synchronizedMap()方法后会返回一个SynchronizedMap类的对象,而在SynchronizedMap类中使用了synchronized同步关键字来保证对Map的操作是线程安全的。

    性能对比

    这是要靠数据说话的时代,所以不能只靠嘴说CHM快,它就快了。写个测试用例,实际的比较一下这三种方式的效率(源码来源),下面的代码分别通过三种方式创建Map对象,使用ExecutorService来并发运行5个线程,每个线程添加/获取500K个元素。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    public class CrunchifyConcurrentHashMapVsSynchronizedMap {
     
        public final static int THREAD_POOL_SIZE = 5;
     
        public static Map<String, Integer> crunchifyHashTableObject = null;
        public static Map<String, Integer> crunchifySynchronizedMapObject = null;
        public static Map<String, Integer> crunchifyConcurrentHashMapObject = null;
     
        public static void main(String[] args) throws InterruptedException {
     
            // Test with Hashtable Object
            crunchifyHashTableObject = new Hashtable<>();
            crunchifyPerformTest(crunchifyHashTableObject);
     
            // Test with synchronizedMap Object
            crunchifySynchronizedMapObject = Collections.synchronizedMap(new HashMap<String, Integer>());
            crunchifyPerformTest(crunchifySynchronizedMapObject);
     
            // Test with ConcurrentHashMap Object
            crunchifyConcurrentHashMapObject = new ConcurrentHashMap<>();
            crunchifyPerformTest(crunchifyConcurrentHashMapObject);
     
        }
     
        public static void crunchifyPerformTest(final Map<String, Integer> crunchifyThreads) throws InterruptedException {
     
            System.out.println("Test started for: " + crunchifyThreads.getClass());
            long averageTime = 0;
            for (int i = 0; i < 5; i++) {
     
                long startTime = System.nanoTime();
                ExecutorService crunchifyExServer = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
     
                for (int j = 0; j < THREAD_POOL_SIZE; j++) {
                    crunchifyExServer.execute(new Runnable() {
                        @SuppressWarnings("unused")
                        @Override
                        public void run() {
     
                            for (int i = 0; i < 500000; i++) {
                                Integer crunchifyRandomNumber = (int) Math.ceil(Math.random() * 550000);
     
                                // Retrieve value. We are not using it anywhere
                                Integer crunchifyValue = crunchifyThreads.get(String.valueOf(crunchifyRandomNumber));
     
                                // Put value
                                crunchifyThreads.put(String.valueOf(crunchifyRandomNumber), crunchifyRandomNumber);
                            }
                        }
                    });
                }
     
                // Make sure executor stops
                crunchifyExServer.shutdown();
     
                // Blocks until all tasks have completed execution after a shutdown request
                crunchifyExServer.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
     
                long entTime = System.nanoTime();
                long totalTime = (entTime - startTime) / 1000000L;
                averageTime += totalTime;
                System.out.println("2500K entried added/retrieved in " + totalTime + " ms");
            }
            System.out.println("For " + crunchifyThreads.getClass() + " the average time is " + averageTime / 5 + " ms ");
        }
    }

    测试结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    Test started for: class java.util.Hashtable
    2500K entried added/retrieved in 2018 ms
    2500K entried added/retrieved in 1746 ms
    2500K entried added/retrieved in 1806 ms
    2500K entried added/retrieved in 1801 ms
    2500K entried added/retrieved in 1804 ms
    For class java.util.Hashtable the average time is 1835 ms
     
    Test started for: class java.util.Collections$SynchronizedMap
    2500K entried added/retrieved in 3041 ms
    2500K entried added/retrieved in 1690 ms
    2500K entried added/retrieved in 1740 ms
    2500K entried added/retrieved in 1649 ms
    2500K entried added/retrieved in 1696 ms
    For class java.util.Collections$SynchronizedMap the average time is 1963 ms
     
    Test started for: class java.util.concurrent.ConcurrentHashMap
    2500K entried added/retrieved in 738 ms
    2500K entried added/retrieved in 696 ms
    2500K entried added/retrieved in 548 ms
    2500K entried added/retrieved in 1447 ms
    2500K entried added/retrieved in 531 ms
    For class java.util.concurrent.ConcurrentHashMap the average time is 792 ms

    这个就不用废话了,CHM性能是明显优于Hashtable和SynchronizedMap的,CHM花费的时间比前两个的一半还少

  • 相关阅读:
    MKMapVIew学习系列2 在地图上绘制出你运行的轨迹
    WPF SDK研究 Intro(6) WordGame1
    WPF SDK研究 Intro(3) QuickStart3
    WPF SDK研究 Layout(1) Grid
    WPF SDK研究 目录 前言
    WPF SDK研究 Intro(7) WordGame2
    WPF SDK研究 Layout(2) GridComplex
    对vs2005创建的WPF模板分析
    WPF SDK研究 Intro(4) QuickStart4
    《Programming WPF》翻译 第6章 资源
  • 原文地址:https://www.cnblogs.com/duanxiaojun/p/6826987.html
Copyright © 2011-2022 走看看