ConcurrentMap
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相关推荐
- javacurrentmap_Java 8 并发: 原子变量和 ConcurrentMap
AtomicInteger java.concurrent.atomic 包下有很多原子操作的类. 在有些情况下,原子操作可以在不使用 synchronized 关键字和锁的情况下解决多线程安全问题. ...
- ConcurrentMap接口
ConcurrentMap接口 两个实现 ConcurrentHashMap ConcurrentSkipListMap 支持并发排序功能,弥补ConcurrentHashMap Concurrent ...
- java concurrentmap原理_Java集合番外篇 -- ConcurrentHashMap底层实现和原理
概述 距离上一次集合篇结束已经过了好久了, 之前说要写一下番外,但是太忙了,总也找不出相对松散的时间,也有点静不下心来,最近花了点时间,于是便有了这篇博客. 在开始之前先介绍一个算法, 这个算法和Co ...
- go 分段锁ConcurrentMap,map+读写锁,sync.map的效率测试
分段锁ConcurrentMap的实现请参考笔者的另外一篇博客: go 分段锁ConcurrentMap的实现源码 效率测试结论: 1. go自带的map不是多协程安全的 2. 分段锁Concurre ...
- Java 8 并发: 原子变量和 ConcurrentMap
原文地址: Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap AtomicInteger java.concurrent. ...
- Java并发包:ConcurrentMap
转载自 Java并发包:ConcurrentMap 文章译自:http://tutorials.jenkov.com/java-util-concurrent/index.html 抽空翻译了一下 ...
- Java多线程——ConcurrentMap、ConcurrentHashMap
ConcurrentMap是Map的子接口,是高并发下线程安全的Map集合. public interface ConcurrentMap<K, V> extends Map<K, ...
- Map 和ConcurrentMap 线程不安全和线程安全证明
背景:Map 线程不安全,ConcurrentMap 线程安全 证明方式:通过单词计数,多线程记录40000个单词,key 代表单词 "work",value 代表计数值 代码: ...
- ConcurrentMap的putIfAbsent与put的区别
首先putIfAbsent与put都可以往map中添加内容,但它们在添加的时候有着很大的不同,一不注意,就会采坑.putIfAbsent在添加的时候会验证hash值,如果hash值一样,则不会更新va ...
最新文章
- canvas java 上传截图_在Vue项目中使用html2canvas生成页面截图并上传
- 3d slicer调整窗宽窗位_3D人脸模型月销量上千单,谁在打印,谁在帮打?
- 并发编程下的性能定律(翻译)
- Nginx面试中最常见的18道题及答案
- php 通过坐标获取省市,PHP根据经纬度获取在范围坐标的数据
- Rails 3:提高Ajax应用速度
- python中序列类型和数组之间的区别_「Python」序列构成的数组
- PAT 00-自测1. 打印沙漏(20)
- Windows服务创建及安装
- 第二十一天 认识一维数组part3
- Shell工具 cut sed awk sort
- IEEE 会议论文 Latex模板
- 按键精灵手机助手之实战篇(二)防封
- ios 程序中安装 描述文件
- Java核心知识点精心整理(全是精华)
- qt中toLocal8Bit和toUtf8()有什么区别
- TiDB PCTP(PingCAP 认证 TiDB 数据库专家) 认证考试高分攻略
- 公众号配置服务器信息在哪里,公众号服务器配置在哪
- 手机gif图片怎么压缩变小?gif动图怎样缩小?
- 定位器百科:老人、小孩的GPS定位器是如何工作的
热门文章
- Vue移动端项目(二)
- 内部类和匿名类的介绍
- Ubuntu出现 recovering journal ; /dev/sda1: clean, ***/*** files, ***/*** blocks 等信息无法开机的问题
- 【STM32】NVIC中断优先级管理(中断向量表)
- getOutputStream() has already been called for this response 当前响应已经调用了方法getOutputStream()
- c++ const 转非const
- Mac系统brew install 安装报错 Error: Failure while executing
- 被迫选择了到了外包公司
- arm linux fpu,ARM处理器的浮点运算单元(FPU)
- 关于vSphere vMotion的讨论 -3