ConcurrentMap

article/2025/8/23 15:46:00

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
 


http://chatgpt.dhexx.cn/article/02pJ0JKe.shtml

相关文章

学习线程安全队列ConcurrentQueue

首先,基本使用&#xff1a;入队(EnQueue) 、出队(TryDequeue) 、是否为空(IsEmpty)、获取队列内元素数量(Count)。 一、ConcurrentQueue内部结构: 1.实现原理 众所周知&#xff0c;在普通的非线程安全队列有两种实现方式: 1.使用数组实现的循环队列。 2.使用链表实现的队列…

并发系列(六)-----concurrent的简单介绍

一 简介 concurrent包是jdk1.5引入的重要的包&#xff0c;主要代码由大牛Doug Lea完成。这个包下的一些类如果用好了可以很方便的保证数据在多线程下操作的正确性。就比如说线程共享的i&#xff0c;如果使用concurrent包下的Atomic系列类可以很方便的解决这个问题。这篇文章简单…

python并发之concurrent快速入门

导读&#xff1a;我很笨&#xff0c;但是我很快——计算机之所以计算能力如此出众&#xff0c;不在于其有多智能&#xff0c;而是因为它超快的执行速度&#xff0c;而多核心则可以进一步成倍的提高效率。在python中&#xff0c;concurrent库就是用于完成并发的模块之一。 01 初…

Java 并发工具包(concurrent)详解

目录 一、concurrent并发包 二、ReentrantLock&#xff08;可重入锁&#xff09; 1、锁状态中断与可重入 2、尝试非阻塞地获取锁 3、等待可中断 4、设置公平锁 三、CountDownLatch&#xff08;门栓&#xff09; 四、cyclicBarrier&#xff08;栅栏&#xff09; 五、…

JAVA中split函数的用法

JAVA中split函数的用法 只写经常使用的&#xff0c;并不完整。 1.基本用法&#xff0c;将字符串按照指定字符串进行分割&#xff0c;例如&#xff1a; public class Main {public static void main(String[] args) {String ss "abcabcdefg";String[] split ss.sp…

C语言实现split函数

实现类似JAVA编程语言中split函数&#xff1a; &#xff08;这里以空格为分隔符进行演示&#xff09; 函数的声明&#xff1a;void split(char *src,const char *separator,char **dest,int *num) {}变量&#xff1a; 1.*src&#xff1a;要进行分割的字符串地址&#xff0c; 2…

mysql实现自定义split函数

1、自定义split函数脚本 CREATE DEFINER root% FUNCTION tjdemo.fun_get_split_string_total(f_string varchar(1000),f_delimiter varchar(5)) RETURNS int(11) LANGUAGE SQL NOT DETERMINISTIC CONTAINS SQL SQL SECURITY DEFINER COMMENT BEGIN declare returnInt int(11…

Oracle实现split函数

创建TYPE CREATE OR REPLACE TYPE TYPE_SPLIT AS TABLE OF VARCHAR2 (4000);创建函数 CREATE OR REPLACE FUNCTION SPLIT(P_STRING VARCHAR2, P_SEP VARCHAR2 : ,)RETURN TYPE_SPLITPIPELINED ISIDX PLS_INTEGER;V_STRING VARCHAR2(4000) : P_STRING; BEGINLOOPIDX : INSTR(…

java split函数的用法_java中split函数用法以及注意事项

java中split函数用法以及注意事项 发布时间&#xff1a;2020-04-23 10:28:23 来源&#xff1a;亿速云 阅读&#xff1a;215 作者&#xff1a;小新 本篇文章和大家了解一下java中split函数用法以及注意事项。有一定的参考价值&#xff0c;有需要的朋友可以参考一下&#xff0c;希…

mysql 创建函数 split_在mysql中实现split函数的几种方法

在mysql中实现split函数的几种方法 关注:98 答案:2 mip版 解决时间 2021-02-07 11:27 提问者夜落花台 2021-02-07 02:11 在mysql中实现split函数的几种方法 最佳答案 二级知识专家蓝莓九栀 2021-02-07 03:28 mysql 5.* 的版本现在没有split 函数,以下是几个自定义的split函数…

Oracle split函数

一、创建split函数 1、创建TYPE CREATE OR REPLACE TYPE TYPE_SPLIT AS TABLE OF VARCHAR2 (4000); / 2、创建split函数 CREATE OR REPLACE FUNCTION SPLIT(P_STRING VARCHAR2, P_SEP VARCHAR2 : ,)RETURN TYPE_SPLITPIPELINED ISIDX PLS_INTEGER;V_STRING VARCHAR2(4000)…

mysql有split函数么_mysql中split函数

在mysql中并没有split函数,需要自己写: 1)获得按指定字符分割的字符串的个数: Sql代码 DELIMITER$$ DROP FUNCTION IFEXISTS`sims`.`func_get_split_string_total`$$ CREATE DEFINER=`root`@`localhost` FUNCTION `func_get_split_string_total`( f_strin 在mysql中并没有sp…

Python之split函数的详解

目录 一、split函数的官方定义 二、split函数的深刻理解 二、split函数的深刻理解 split函数主要应用场景是Python对字符串的处理中&#xff08;数据分析&#xff0c;数据处理&#xff09;&#xff0c;以及计算机二级考试的常考基础知识点。 一、split函数的官方定义 定义…

Python基础之split()函数

一、split()函数描述 split() 通过指定分隔符对字符串进行切片&#xff0c;如果参数 num 有指定值&#xff0c;则分隔 num1 个子字符串split() 方法语法&#xff1a; 二、split()用法 语法&#xff1a; str.split(str"", numstring.count(str)) 参数&#xff1a; str…

分割字符串split函数的正确用法(切片)

分割字符串split函数的正确用法&#xff08;切片&#xff09; split函数是将字符串分割为列表 函数原型&#xff1a; str.split(sep,maxsplit)参数说明&#xff1a; str:表示要进行分割的字符串sep:用于指定分隔符&#xff0c;可以包含多个字符&#xff0c;默认为None,即所有…

PostgreSQL 视图

详细了解视图、函数&#xff08;存储过程&#xff09;、触发器、别名 视图篇 一、视图定义 引言: 假设天气记录和城市为止的组合列表对我们的应用有用&#xff0c;但我 们又不想每次需要使用它时都敲入整个查询。我们可以在该查询上创建一个视图&#xff0c;这会给该 查询一…

SQL Server 数据库之视图(二)

视图&#xff08;二&#xff09; 1. 查询视图信息1.1 查询和视图设计工具1.2 关系图视图1.3 条件窗格1.4 SQL 窗格1.5 结果窗格1.6 SQL 编辑器1.7 获取有关视图的信息 2. 创建基于视图的视图3. 删除视图 1. 查询视图信息 1.1 查询和视图设计工具 打开视图的定义、显示查询视图…

hive视图

Hive的视图 应用场景将特定的列提供给用户&#xff0c;保护数据隐私 用于查询语句复杂的场景 通过隐藏子查询、连接和函数来简化查询的逻辑结构只保存定义&#xff0c;不存储数据 如果删除或更改基础表&#xff0c;则查询视图将失败 视图是只读的&#xff0c;不能插入或装载数据…

mysql 查询视图_MySQL查看视图

查看视图 是指查看数据库中&#xff0c;已经存在的视图的定义 查看视图&#xff0c;必须要有SHOW VIEW的权限 查看视图有三种方式 DESCRIBE语句 使用DESCRIBE语句&#xff0c;查看视图 MySQL中&#xff0c;使用DESCRIBE可以查看视图的字段信息&#xff0c;其中&#xff0c;包括…

UML 视图

概述 事物&#xff08;Things&#xff09;、关系(Relationships) 是组成 UML 模型的基本模型元素&#xff0c;图由模型元素事物和关系构成&#xff0c;视图由各种图构成。 随着系统复杂性的增加&#xff0c;建模就成了必不可少的工作。理想情况下&#xff0c;系统由单一的图形…