集合框架—常用的map集合

1、Collections.synchronizedMap()

 

实现上在调用map所有方法时,都对整个map进行同步,而ConcurrentHashMap的实现却更加精细,它对map中的所有桶加了锁。所以,只要要有一个线程访问map,其他线程就无法进入map,而如果一个线程在访问ConcurrentHashMap某个桶时,其他线程,仍然可以对map执行某些操作。这样,ConcurrentHashMap在性能以及安全性方面,明显比Collections.synchronizedMap()更加有优势。同时,同步操作精确控制到桶,所以,即使在遍历map时,其他线程试图对map进行数据修改,也不会抛出ConcurrentModificationException

ConcurrentHashMap从类的命名就能看出,它必然是个HashMapCollections.synchronizedMap()可以接收任意Map实例作为封装。

=====================================================================================

下面的例子显示java.util.Collections.synchronizedMap()方法的使用

package com.yiibai; 

import java.util.*; 

public class CollectionsDemo {

   public static void main(String[] args) {

      // create map

      Map<String,String> map = new HashMap<String,String>();     

      // populate the map

      map.put("1","TP");

      map.put("2","IS");

      map.put("3","BEST");     

      // create a synchronized map

      Map<String,String> synmap = Collections.synchronizedMap(map);    

      System.out.println("Synchronized map is :"+synmap);

   }

}

现在编译和运行上面的代码示例,将产生以下结果。

Synchronized map is :{3=BEST, 2=IS, 1=TP} 

也不会抛出ConcurrentModificationException

===============================================================================

 

2、ConcurrentHashMap

 

1:线程安全,

2:Iterator的迭代方式采用的是fail-safe的错误机制

ConcurrentHashMap在线程安全的基础上提供了更好的写并发能力,但同时降低了对读一致性的要求,ConcurrentHashMap的设计与实现非常精巧,大量的利用了volatile,final,CAS等lock-free技术来减少锁竞争对于性能的影响.

3:ConcurrentHashMap的实现却更加精细,它对map中的所有桶加了锁。

4:同时,同步操作精确控制到桶,所以,即使在遍历map时,其他线程试图对map进行数据修改,也不会抛出ConcurrentModificationException

 

ConcurrentHashMap采用了分段锁的设计,只有在同一个分段内才存在竞态关系,不同的分段锁之间没有锁竞争。相比于对整个Map加锁的设计,分段锁大大的提高了高并发环境下的处理能力。但同时,由于不是对整个Map加锁,导致一些需要扫描整个Map的方法(如size(), containsValue())需要使用特殊的实现,另外一些方法(如clear())甚至放弃了对一致性的要求(ConcurrentHashMap是弱一致性的,

ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

源代码:

static final class Segment<K,V> extends ReentrantLock implements Serializable{ 

并发度

并发度可以理解为程序运行时能够同时更新ConccurentHashMap且不产生锁竞争的最大线程数,实际上就是ConcurrentHashMap中的分段锁个数,即Segment[]的数组长度。ConcurrentHashMap默认的并发度为16,但用户也可以在构造函数中设置并发度。当用户设置并发度时,ConcurrentHashMap会使用大于等于该值的最小2幂指数作为实际并发度(假如用户设置并发度为17,实际并发度则为32)。运行时通过将key的高n位(n = 32 – segmentShift)和并发度减1(segmentMask)做位与运算定位到所在的Segment。segmentShift与segmentMask都是在构造过程中根据concurrency level被相应的计算出来。

如果并发度设置的过小,会带来严重的锁竞争问题;如果并发度设置的过大,原本位于同一个Segment内的访问会扩散到不同的Segment中,CPU cache命中率会下降,从而引起程序性能下降。(文档的说法是根据你并发的线程数量决定,太多会导性能降低)

JDK8中的实现

ConcurrentHashMap在JDK8中进行了巨大改动,很需要通过源码来再次学习下Doug Lea的实现方法。

它摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法。它沿用了与它同时期的HashMap版本的思想,底层依然由"数组"+链表+红黑树的方式思想(JDK7与JDK8中HashMap的实现),但是为了做到并发,又增加了很多辅助的类,例如TreeBin,Traverser等对象内部类。

重要的属性

首先来看几个重要的属性,与HashMap相同的就不再介绍了,这里重点解释一下sizeCtl这个属性。可以说它是ConcurrentHashMap中出镜率很高的一个属性,因为它是一个控制标识符,在不同的地方有不同用途,而且它的取值不同,也代表不同的含义。

  • 负数代表正在进行初始化或扩容操作
  • -1代表正在初始化
  • -N 表示有N-1个线程正在进行扩容操作
  • 正数或0代表hash表还没有被初始化,这个数值表示初始化或下一次进行扩容的大小,这一点类似于扩容阈值的概念。还后面可以看到,它的值始终是当前ConcurrentHashMap容量的0.75倍,这与loadfactor是对应的。

总结

JDK6,7中的ConcurrentHashmap主要使用Segment来实现减小锁粒度,把HashMap分割成若干个Segment,在put的时候需要锁住Segment,get时候不加锁,使用volatile来保证可见性,当要统计全局时(比如size),首先会尝试多次计算modcount来确定,这几次尝试中,是否有其他线程进行了修改操作,如果没有,则直接返回size。如果有,则需要依次锁住所有的Segment来计算。

jdk7中ConcurrentHashmap中,当长度过长碰撞会很频繁,链表的增改删查操作都会消耗很长的时间,影响性能,所以jdk8 中完全重写了concurrentHashmap,代码量从原来的1000多行变成了 6000多 行,实现上也和原来的分段式存储有很大的区别。

主要设计上的变化有以下几点:

  1. 不采用segment而采用node,锁住node来实现减小锁粒度。
  2. 设计了MOVED状态 当resize的中过程中 线程2还在put数据,线程2会帮助resize。
  3. 使用3个CAS操作来确保node的一些操作的原子性,这种方式代替了锁。
  4. sizeCtl的不同值来代表不同含义,起到了控制的作用。

至于为什么JDK8中使用synchronized而不是ReentrantLock,我猜是因为JDK8中对synchronized有了足够的优化吧。

 

3、LinkedHashMap

 

HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。

 

Map<Integer,String> map = new LinkedHashMap<Integer,String>();

   map.put(6, "apple");

   map.put(3, "banana");

   map.put(2,"pear");

   

   

for (Iterator it =  map.keySet().iterator();it.hasNext();)

   {

    Object key = it.next();

    System.out.println( key+"="+ map.get(key));

   }

运行结果如下:

*************************LinkedHashMap*************

6=apple

3=banana

2=pear

 

 

4、TreeMap

 

1TreeMap 类不仅实现了 Map 接口,还实现了 Map 接口的子接口 java.util.SortedMap 

2TreeMap 类中不允许键对象为 null 或是基本数据类型,这是因为 TreeMap 中的对象必须是可排序的(即对象需要实现 java.lang.Comparable 接口) 

3:在创建 TreeMap 对象时,如果使用参数为空的构造方法,则根据 Map 对象的 key 进行排序;如果使用参数为 Comparator 的构造方法,则根据 Comparator 进行排序。 

4:在添加、删除和定位映射关系上,TreeMap类要比HashMap类的性能差一些,

5:在需要排序时,利用现有的 HashMap,创建一个 TreeMap 类型的实例

 

Java代码  

  1. import java.util.Collections;  
  2. import java.util.HashMap;  
  3. import java.util.Iterator;  
  4. import java.util.Map;  
  5. import java.util.TreeMap;  
  6.   
  7. public class TestCollection {  
  8.   
  9.     public static void main(String[] args) {  
  10.         System.out.println("开始:");    
  11.                 
  12.         Person person1 = new Person("马先生"220181);  
  13.         Person person2 = new Person("李先生"220193);  
  14.         Person person3 = new Person("王小姐"220186);  
  15.           
  16.         Map<Number, Person> map = new HashMap<Number, Person>();  
  17.         map.put(person1.getId_card(), person1);  
  18.         map.put(person2.getId_card(), person2);  
  19.         map.put(person3.getId_card(), person3);  
  20.           
  21.         // HashMap  
  22.         System.out.println("HashMap,无序:");  
  23.         for (Iterator<Number> it = map.keySet().iterator(); it.hasNext();) {  
  24.             Person person = map.get(it.next());  
  25.             System.out.println(person.getId_card() + " " + person.getName());  
  26.         }  
  27.           
  28.         // TreeMap  
  29.         System.out.println("TreeMap,升序:");  
  30. //创建 TreeMap 时,如果使用参数为空的构造方法,则根据 Map 对象的 key 进行排序
  31.         TreeMap<Number, Person> treeMap = new TreeMap<Number, Person>();  
  32.         //将指定映射中的所有映射关系复制到此映射中。
  33. treeMap.putAll(map);           for (Iterator<Number> it = treeMap.keySet().iterator(); it.hasNext();) {  
  34.             Person person = treeMap.get(it.next());  
  35.             System.out.println(person.getId_card() + " " + person.getName());  
  36.         }  
  37.           
  38.         System.out.println("TreeMap,降序:");
  39. //Collections.reverseOrder()返回一个比较器,它强行逆转指定比较器顺序 
  40. //如果使用参数为 Comparator 的构造方法,则根据 Comparator 进行排序。  
  41.         TreeMap<Number, Person> treeMap2 =   
  42.             new TreeMap<Number, Person>(Collections.reverseOrder());  
  43.         treeMap2.putAll(map);  
  44.         for (Iterator it = treeMap2.keySet().iterator(); it.hasNext();) {  
  45.             Person person = (Person) treeMap2.get(it.next());  
  46.             System.out.println(person.getId_card() + " " + person.getName());  
  47.         }  
  48.           
  49.         System.out.println("结束!");  
  50.     }  
  51. }  

 

posted @ 2017-12-16 17:13  老人与JAVA  阅读(1442)  评论(0编辑  收藏  举报