ConcurrentMap,它是一个接口,是一个能够支持并发访问的java.util.map集合;

ConcurrentHashMap是一个线程安全,并且是一个高效的HashMap。

spring 缓存注解 通过查看源代码发现将数据存在ConcurrentMap中

1 Map并发集合

1.1 ConcurrentMap

ConcurrentMap,它是一个接口,是一个能够支持并发访问的java.util.map集合;

在原有java.util.map接口基础上又新提供了4种方法,进一步扩展了原有Map的功能:

public interface ConcurrentMap<K, V> extends Map<K, V> {//插入元素V putIfAbsent(K key, V value);//移除元素boolean remove(Object key, Object value);//替换元素boolean replace(K key, V oldValue, V newValue);//替换元素V replace(K key, V value);
}

putIfAbsent:与原有put方法不同的是,putIfAbsent方法中如果插入的key相同,则不替换原有的value值;

remove:与原有remove方法不同的是,新remove方法中增加了对value的判断,如果要删除的key--value不能与Map中原有的key--value对应上,则不会删除该元素;

replace(K,V,V):增加了对value值的判断,如果key--oldValue能与Map中原有的key--value对应上,才进行替换操作;

replace(K,V):与上面的replace不同的是,此replace不会对Map中原有的key--value进行比较,如果key存在则直接替换;

其实,对于ConcurrentMap来说,我们更关注Map本身的操作,在并发情况下是如何实现数据安全的。在java.util.concurrent包中,ConcurrentMap的实现类主要以ConcurrentHashMap为主。接下来,我们具体来看下。

1.2 ConcurrentHashMap

ConcurrentHashMap是一个线程安全,并且是一个高效的HashMap。

但是,如果从线程安全的角度来说,HashTable已经是一个线程安全的HashMap,那推出ConcurrentHashMap的意义又是什么呢?

说起ConcurrentHashMap,就不得不先提及下HashMap在线程不安全的表现,以及HashTable的效率!

  • HashMap

关于HashMap的讲解,在此前的文章中已经说过了,本篇不在做过多的描述,有兴趣的朋友可以来这里看下--HashMap。

在此节中,我们主要来说下,在多线程情况下HashMap的表现?

HashMap中添加元素的源码:(基于JDK1.7.0_45)

public V put(K key, V value) {。。。忽略addEntry(hash, key, value, i);return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {。。。忽略createEntry(hash, key, value, bucketIndex);
}
//向链表头部插入元素:在数组的某一个角标下形成链表结构;
void createEntry(int hash, K key, V value, int bucketIndex) {Entry<K,V> e = table[bucketIndex];table[bucketIndex] = new Entry<>(hash, key, value, e);size++;
}

在多线程情况下,同时A、B两个线程走到createEntry()方法中,并且这两个线程中插入的元素hash值相同,bucketIndex值也相同,那么无论A线程先执行,还是B线程先被执行,最终都会2个元素先后向链表的头部插入,导致互相覆盖,致使其中1个线程中的数据丢失。这样就造成了HashMap的线程不安全,数据的不一致;

更要命的是,HashMap在多线程情况下还会出现死循环的可能,造成CPU占用率升高,导致系统卡死。

举个简单的例子:

public class ConcurrentHashMapTest {public static void main(String[] agrs) throws InterruptedException {final HashMap<String,String> map = new HashMap<String,String>();Thread t = new Thread(new Runnable(){public  void run(){for(int x=0;x<10000;x++){Thread tt = new Thread(new Runnable(){public void run(){map.put(UUID.randomUUID().toString(),"");}});tt.start();System.out.println(tt.getName());}}});t.start();t.join();}
}

在上面的例子中,我们利用for循环,启动了10000个线程,每个线程都向共享变量中添加一个元素。

测试结果:通过使用JDK自带的jconsole工具,可以看到HashMap内部形成了死循环,并且主要集中在两处代码上。

image

image

那么,是什么原因造成了死循环?

HashMap--put()494行:(基于JDK1.7.0_45)

public V put(K key, V value) {if (table == EMPTY_TABLE) {inflateTable(threshold);}if (key == null)return putForNullKey(value);int hash = hash(key);int i = indexFor(hash, table.length);for (Entry<K,V> e = table[i]; e != null; e = e.next) {------**for循环494行**Object k;if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}modCount++;addEntry(hash, key, value, i);return null;
}

HashMap--transfer()601行:(基于JDK1.7.0_45)

void transfer(Entry[] newTable, boolean rehash) {int newCapacity = newTable.length;for (Entry<K,V> e : table) {while(null != e) {Entry<K,V> next = e.next;if (rehash) {e.hash = null == e.key ? 0 : hash(e.key);}int i = indexFor(e.hash, newCapacity);e.next = newTable[i];newTable[i] = e;e = next;}-----**while循环601行**}
}

通过查看代码,可以看出,死循环的产生:主要因为在遍历数组角标下的链表时,没有了为null的元素,单向链表变成了循环链表,头尾相连了。

以上两点,就是HashMap在多线程情况下的表现。

  • HashTable

说完了HashMap的线程不安全,接下来说下HashTable的效率!!

HashTable与HashMap的结构一致,都是哈希表实现。

与HashMap不同的是,在HashTable中,所有的方法都加上了synchronized锁,用锁来实现线程的安全性。由于synchronized锁加在了HashTable的每一个方法上,所以这个锁就是HashTable本身--this。那么,可想而知HashTable的效率是如何,安全是保证了,但是效率却损失了。

无论执行哪个方法,整个哈希表都会被锁住,只有其中一个线程执行完毕,释放所,下一个线程才会执行。无论你是调用get方法,还是put方法皆是如此;

HashTable部分源码:(基于JDK1.7.0_45)

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {public synchronized int size() {...}public synchronized boolean isEmpty() {...}public synchronized V get(Object key) {...}public synchronized V put(K key, V value) {...}
}

通过上述代码,可以清晰看出,在HashTable中的主要操作方法上都加了synchronized锁以来保证线程安全。

说完了HashMap和HashTable,下面我们就重点介绍下ConcurrentHashMap,看看ConcurrentHashMap是如何来解决上述的两个问题的!

1.3 ConcurrentHashMap结构

在说到ConcurrentHashMap源码之前,我们首先来了解下ConcurrentHashMap的整体结构,这样有利于我们快速理解源码。

不知道,大家还是否记得HashMap的整体结构呢?如果忘记的话,我们就在此进行回顾下!

image

HashMap底层使用数组和链表,实现哈希表结构。插入的元素通过散列的形式分布到数组的各个角标下;当有重复的散列值时,便将新增的元素插入在链表头部,使其形成链表结构,依次向后排列。

下面是,ConcurrentHashMap的结构:

image

与HashMap不同的是,ConcurrentHashMap中多了一层数组结构,由Segment和HashEntry两个数组组成。其中Segment起到了加锁同步的作用,而HashEntry则起到了存储K.V键值对的作用。

在ConcurrentHashMap中,每一个ConcurrentHashMap都包含了一个Segment数组,在Segment数组中每一个Segment对象则又包含了一个HashEntry数组,而在HashEntry数组中,每一个HashEntry对象保存K-V数据的同时又形成了链表结构,此时与HashMap结构相同。

在多线程中,每一个Segment对象守护了一个HashEntry数组,当对ConcurrentHashMap中的元素修改时,在获取到对应的Segment数组角标后,都会对此Segment对象加锁,之后再去操作后面的HashEntry元素,这样每一个Segment对象下,都形成了一个小小的HashMap,在保证数据安全性的同时,又提高了同步的效率。只要不是操作同一个Segment对象的话,就不会出现线程等待的问题!

链接:https://www.jianshu.com/p/8f7b2cd34c47

ConcurrentMap相关推荐

  1. javacurrentmap_Java 8 并发: 原子变量和 ConcurrentMap

    AtomicInteger java.concurrent.atomic 包下有很多原子操作的类. 在有些情况下,原子操作可以在不使用 synchronized 关键字和锁的情况下解决多线程安全问题. ...

  2. ConcurrentMap接口

    ConcurrentMap接口 两个实现 ConcurrentHashMap ConcurrentSkipListMap 支持并发排序功能,弥补ConcurrentHashMap Concurrent ...

  3. java concurrentmap原理_Java集合番外篇 -- ConcurrentHashMap底层实现和原理

    概述 距离上一次集合篇结束已经过了好久了, 之前说要写一下番外,但是太忙了,总也找不出相对松散的时间,也有点静不下心来,最近花了点时间,于是便有了这篇博客. 在开始之前先介绍一个算法, 这个算法和Co ...

  4. go 分段锁ConcurrentMap,map+读写锁,sync.map的效率测试

    分段锁ConcurrentMap的实现请参考笔者的另外一篇博客: go 分段锁ConcurrentMap的实现源码 效率测试结论: 1. go自带的map不是多协程安全的 2. 分段锁Concurre ...

  5. Java 8 并发: 原子变量和 ConcurrentMap

    原文地址: Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap AtomicInteger java.concurrent. ...

  6. Java并发包:ConcurrentMap

    转载自  Java并发包:ConcurrentMap 文章译自:http://tutorials.jenkov.com/java-util-concurrent/index.html  抽空翻译了一下 ...

  7. Java多线程——ConcurrentMap、ConcurrentHashMap

    ConcurrentMap是Map的子接口,是高并发下线程安全的Map集合. public interface ConcurrentMap<K, V> extends Map<K, ...

  8. Map 和ConcurrentMap 线程不安全和线程安全证明

    背景:Map 线程不安全,ConcurrentMap 线程安全 证明方式:通过单词计数,多线程记录40000个单词,key 代表单词 "work",value 代表计数值 代码: ...

  9. ConcurrentMap的putIfAbsent与put的区别

    首先putIfAbsent与put都可以往map中添加内容,但它们在添加的时候有着很大的不同,一不注意,就会采坑.putIfAbsent在添加的时候会验证hash值,如果hash值一样,则不会更新va ...

最新文章

  1. canvas java 上传截图_在Vue项目中使用html2canvas生成页面截图并上传
  2. 3d slicer调整窗宽窗位_3D人脸模型月销量上千单,谁在打印,谁在帮打?
  3. 并发编程下的性能定律(翻译)
  4. Nginx面试中最常见的18道题及答案
  5. php 通过坐标获取省市,PHP根据经纬度获取在范围坐标的数据
  6. Rails 3:提高Ajax应用速度
  7. python中序列类型和数组之间的区别_「Python」序列构成的数组
  8. PAT 00-自测1. 打印沙漏(20)
  9. Windows服务创建及安装
  10. 第二十一天 认识一维数组part3
  11. Shell工具 cut sed awk sort
  12. IEEE 会议论文 Latex模板
  13. 按键精灵手机助手之实战篇(二)防封
  14. ios 程序中安装 描述文件
  15. Java核心知识点精心整理(全是精华)
  16. qt中toLocal8Bit和toUtf8()有什么区别
  17. TiDB PCTP(PingCAP 认证 TiDB 数据库专家) 认证考试高分攻略
  18. 公众号配置服务器信息在哪里,公众号服务器配置在哪
  19. 手机gif图片怎么压缩变小?gif动图怎样缩小?
  20. 定位器百科:老人、小孩的GPS定位器是如何工作的

热门文章

  1. Vue移动端项目(二)
  2. 内部类和匿名类的介绍
  3. Ubuntu出现 recovering journal ; /dev/sda1: clean, ***/*** files, ***/*** blocks 等信息无法开机的问题
  4. 【STM32】NVIC中断优先级管理(中断向量表)
  5. getOutputStream() has already been called for this response 当前响应已经调用了方法getOutputStream()
  6. c++ const 转非const
  7. Mac系统brew install 安装报错 Error: Failure while executing
  8. 被迫选择了到了外包公司
  9. arm linux fpu,ARM处理器的浮点运算单元(FPU)
  10. 关于vSphere vMotion的讨论 -3