HashMap和ConcurrentHashMap原理及源码解读

ConcurrentHashMap和HashMap的区别 提供了在多线程环境中安全、高效地进行哈希表操作的机制,适用于需要并发访问的场景。HashMap是一个灵活、高性能的数据结构,适用于需要通过键值对进行查找、插入和删除操作的场景相对于HashMap在并发环境下提供了更好的线程安全性和性能。在多线程并发访问的场景中,特别是读写操作频繁的情况下,ConcurrentHashMap更适合使用。而在单线程环境下或者无并发访问需求的情况下,可以考虑使用普通的 HashMap。 阅读详情

前言

Map 这样的 Key Value 在软件开发中是非常经典的结构,常用于在内存中存放数据。

本篇主要想讨论 ConcurrentHashMap 这样一个并发容器,在正式开始之前我觉得有必要谈谈 HashMap,没有它就不会有后面的 ConcurrentHashMap。

HashMap

众所周知 HashMap 底层是基于 数组 + 链表 组成的,不过在 jdk1.7 和 1.8 中具体实现稍有不同。

Base 1.7

1.7 中的数据结构图:

先来看看 1.7 中的实现。

这是 HashMap 中比较核心的几个成员变量;看看分别是什么意思?

  1. 初始化桶大小,因为底层是数组,所以这是数组默认的大小。
  2. 桶最大值。
  3. 默认的负载因子(0.75)
  4. table 真正存放数据的数组。
  5. Map 存放数量的大小。
  6. 桶大小,可在初始化时显式指定。
  7. 负载因子,可在初始化时显式指定。

重点解释下负载因子:

由于给定的 HashMap 的容量大小是固定的,比如默认初始化:

public HashMap() {

this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);

}

public HashMap(int initialCapacity, float loadFactor) {

if (initialCapacity < 0)

throw new IllegalArgumentException("Illegal initial capacity: " +

initialCapacity);

if (initialCapacity > MAXIMUM_CAPACITY)

initialCapacity = MAXIMUM_CAPACITY;

if (loadFactor <= 0 || Float.isNaN(loadFactor))

throw new IllegalArgumentException("Illegal load factor: " +

loadFactor);

this.loadFactor = loadFactor;

threshold = initialCapacity;

init();

}

给定的默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。

因此通常建议能提前预估 HashMap 的大小最好,尽量的减少扩容带来的性能损耗。

根据代码可以看到其实真正存放数据的是

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

这个数组,那么它又是如何定义的呢?

Entry 是 HashMap 中的一个内部类,从他的成员变量很容易看出:

  • key 就是写入时的键。
  • value 自然就是值。
  • 开始的时候就提到 HashMap 是由数组和链表组成,所以这个 next 就是用于实现链表结构。
  • hash 存放的是当前 key 的 hashcode。

知晓了基本结构,那来看看其中重要的写入、获取函数:

put 方法

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) {

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;

}
  • 判断当前数组是否需要初始化。
  • 如果 key 为空,则 put 一个空值进去。
  • 根据 key 计算出 hashcode。
  • 根据计算出的 hashcode 定位出所在桶。
  • 如果桶是一个链表则需要遍历判断里面的 hashcode、key 是否和传入 key 相等,如果相等则进行覆盖,并返回原来的值。
  • 如果桶是空的,说明当前位置没有数据存入;新增一个 Entry 对象写入当前位置。
    void addEntry(int hash, K key, V value, int bucketIndex) {
    
    if ((size >= threshold) && (null != table[bucketIndex])) {
    
    resize(2 * table.length);
    
    hash = (null != key) ? hash(key) : 0;
    
    bucketIndex = indexFor(hash, table.length);
    
    }
    
    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++;
    
    }

     

当调用 addEntry 写入 Entry 时需要判断是否需要扩容。

如果需要就进行两倍扩充,并将当前的 key 重新 hash 并定位。

而在 createEntry 中会将当前位置的桶传入到新建的桶中,如果当前桶有值就会在位置形成链表。

get 方法

再来看看 get 函数:

public V get(Object key) {

if (key == null)

return getForNullKey();

Entry<K,V> entry = getEntry(key);

return null == entry ? null : entry.getValue();

}

final Entry<K,V> getEntry(Object key) {

if (size == 0) {

return null;

}

int hash = (key == null) ? 0 : hash(key);

for (Entry<K,V> e = table[indexFor(hash, table.length)];

e != null;

e = e.next) {

Object k;

if (e.hash == hash &&

((k = e.key) == key || (key != null && key.equals(k))))

return e;

}

return null;

}
  • 首先也是根据 key 计算出 hashcode,然后定位到具体的桶中。
  • 判断该位置是否为链表。
  • 不是链表就根据 key、key 的 hashcode 是否相等来返回值。
  • 为链表则需要遍历直到 key 及 hashcode 相等时候就返回值。
  • 啥都没取到就直接返回 null 。

Base 1.8

不知道 1.7 的实现大家看出需要优化的点没有?

其实一个很明显的地方就是:

当 Hash 冲突严重时,在桶上形成的链表会变的越来越长,这样在查询时的效率就会越来越低;时间复杂度为 O(N)。

因此 1.8 中重点优化了这个查询效率。

1.8 HashMap 结构图:

先来看看几个核心的成员变量:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

/**

* The maximum capacity, used if a higher value is implicitly specified

* by either of the constructors with arguments.

* MUST be a power of two <= 1<<30.

*/

static final int MAXIMUM_CAPACITY = 1 << 30;

/**

* The load factor used when none specified in constructor.

*/

static final float DEFAULT_LOAD_FACTOR = 0.75f;

static final int TREEIFY_THRESHOLD = 8;

transient Node<K,V>[] table;

/**

* Holds cached entrySet(). Note that AbstractMap fields are used

* for keySet() and values().

*/

transient Set<Map.Entry<K,V>> entrySet;

/**

* The number of key-value mappings contained in this map.

*/

transient int size;

和 1.7 大体上都差不多,还是有几个重要的区别:

  • TREEIFY_THRESHOLD 用于判断是否需要将链表转换为红黑树的阈值。
  • HashEntry 修改为 Node。

Node 的核心组成其实也是和 1.7 中的 HashEntry 一样,存放的都是 key value hashcode next 等数据。

再来看看核心方法。

put 方法

看似要比 1.7 的复杂,我们一步步拆解:

  1. 判断当前桶是否为空,空的就需要初始化(resize 中会判断是否进行初始化)。
  2. 根据当前 key 的 hashcode 定位到具体的桶中并判断是否为空,为空表明没有 Hash 冲突就直接在当前位置创建一个新桶即可。
  3. 如果当前桶有值( Hash 冲突),那么就要比较当前桶中的 key、key 的 hashcode 与写入的 key 是否相等,相等就赋值给 e,在第 8 步的时候会统一进行赋值及返回。
  4. 如果当前桶为红黑树,那就要按照红黑树的方式写入数据。
  5. 如果是个链表,就需要将当前的 key、value 封装成一个新节点写入到当前桶的后面(形成链表)。
  6. 接着判断当前链表的大小是否大于预设的阈值,大于时就要转换为红黑树。
  7. 如果在遍历过程中找到 key 相同时直接退出遍历。
  8. 如果 e != null 就相当于存在相同的 key,那就需要将值覆盖。
  9. 最后判断是否需要进行扩容。

get 方法

 

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

 

public V get(Object key) {

Node<K,V> e;

return (e = getNode(hash(key), key)) == null ? null : e.value;

}

final Node<K,V> getNode(int hash, Object key) {

Node<K,V>[] tab; Node<K,V> first, e; int n; K k;

if ((tab = table) != null && (n = tab.length) > 0 &&

(first = tab[(n - 1) & hash]) != null) {

if (first.hash == hash && // always check first node

((k = first.key) == key || (key != null && key.equals(k))))

return first;

if ((e = first.next) != null) {

if (first instanceof TreeNode)

return ((TreeNode<K,V>)first).getTreeNode(hash, key);

do {

if (e.hash == hash &&

((k = e.key) == key || (key != null && key.equals(k))))

return e;

} while ((e = e.next) != null);

}

}

return null;

}

get 方法看起来就要简单许多了。

  • 首先将 key hash 之后取得所定位的桶。
  • 如果桶为空则直接返回 null 。
  • 否则判断桶的第一个位置(有可能是链表、红黑树)的 key 是否为查询的 key,是就直接返回 value。
  • 如果第一个不匹配,则判断它的下一个是红黑树还是链表。
  • 红黑树就按照树的查找方式返回值。
  • 不然就按照链表的方式遍历匹配返回值。

从这两个核心方法(get/put)可以看出 1.8 中对大链表做了优化,修改为红黑树之后查询效率直接提高到了 O(logn)。

但是 HashMap 原有的问题也都存在,比如在并发场景下使用时容易出现死循环。

 

1

2

3

4

5

6

7

8

9

 

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

for (int i = 0; i < 1000; i++) {

new Thread(new Runnable() {

@Override

public void run() {

map.put(UUID.randomUUID().toString(), "");

}

}).start();

}

但是为什么呢?简单分析下。

看过上文的还记得在 HashMap 扩容的时候会调用 resize() 方法,就是这里的并发操作容易在一个桶上形成环形链表;这样当获取一个不存在的 key 时,计算出的 index 正好是环形链表的下标就会出现死循环。

如下图:

遍历方式

还有一个值得注意的是 HashMap 的遍历方式,通常有以下几种:

 

1

2

3

4

5

6

7

8

9

10

11

12

 

Iterator<Map.Entry<String, Integer>> entryIterator = map.entrySet().iterator();

while (entryIterator.hasNext()) {

Map.Entry<String, Integer> next = entryIterator.next();

System.out.println("key=" + next.getKey() + " value=" + next.getValue());

}

Iterator<String> iterator = map.keySet().iterator();

while (iterator.hasNext()){

String key = iterator.next();

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

}

强烈建议使用第一种 EntrySet 进行遍历。

第一种可以把 key value 同时取出,第二种还得需要通过 key 取一次 value,效率较低。

简单总结下 HashMap:无论是 1.7 还是 1.8 其实都能看出 JDK 没有对它做任何的同步操作,所以并发会出问题,甚至 1.7 中出现死循环导致系统不可用(1.8 已经修复死循环问题)。

因此 JDK 推出了专项专用的 ConcurrentHashMap ,该类位于 java.util.concurrent 包下,专门用于解决并发问题。

坚持看到这里的朋友算是已经把 ConcurrentHashMap 的基础已经打牢了,下面正式开始分析。

ConcurrentHashMap

ConcurrentHashMap 同样也分为 1.7 、1.8 版,两者在实现上略有不同。

Base 1.7

先来看看 1.7 的实现,下面是他的结构图:

如图所示,是由 Segment 数组、HashEntry 组成,和 HashMap 一样,仍然是数组加链表。

它的核心成员变量:

 

1

2

3

4

5

6

7

 

/**

* Segment 数组,存放数据时首先需要定位到具体的 Segment 中。

*/

final Segment<K,V>[] segments;

transient Set<K> keySet;

transient Set<Map.Entry<K,V>> entrySet;

Segment 是 ConcurrentHashMap 的一个内部类,主要的组成如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

 

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

private static final long serialVersionUID = 2249069246763182397L;

// 和 HashMap 中的 HashEntry 作用一样,真正存放数据的桶

transient volatile HashEntry<K,V>[] table;

transient int count;

transient int modCount;

transient int threshold;

final float loadFactor;

}

看看其中 HashEntry 的组成:

和 HashMap 非常类似,唯一的区别就是其中的核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。

原理上来说:ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 ReentrantLock。不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。

下面也来看看核心的 put get 方法。

put 方法

1

2

3

4

5

6

7

8

9

10

11

 

public V put(K key, V value) {

Segment<K,V> s;

if (value == null)

throw new NullPointerException();

int hash = hash(key);

int j = (hash >>> segmentShift) & segmentMask;

if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck

(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment

s = ensureSegment(j);

return s.put(key, hash, value, false);

}

首先是通过 key 定位到 Segment,之后在对应的 Segment 中进行具体的 put。

 

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

 

final V put(K key, int hash, V value, boolean onlyIfAbsent) {

HashEntry<K,V> node = tryLock() ? null :

scanAndLockForPut(key, hash, value);

V oldValue;

try {

HashEntry<K,V>[] tab = table;

int index = (tab.length - 1) & hash;

HashEntry<K,V> first = entryAt(tab, index);

for (HashEntry<K,V> e = first;;) {

if (e != null) {

K k;

if ((k = e.key) == key ||

(e.hash == hash && key.equals(k))) {

oldValue = e.value;

if (!onlyIfAbsent) {

e.value = value;

++modCount;

}

break;

}

e = e.next;

}

else {

if (node != null)

node.setNext(first);

else

node = new HashEntry<K,V>(hash, key, value, first);

int c = count + 1;

if (c > threshold && tab.length < MAXIMUM_CAPACITY)

rehash(node);

else

setEntryAt(tab, index, node);

++modCount;

count = c;

oldValue = null;

break;

}

}

} finally {

unlock();

}

return oldValue;

}

虽然 HashEntry 中的 value 是用 volatile 关键词修饰的,但是并不能保证并发的原子性,所以 put 操作时仍然需要加锁处理。

首先第一步的时候会尝试获取锁,如果获取失败肯定就有其他线程存在竞争,则利用 scanAndLockForPut() 自旋获取锁。

  1. 尝试自旋获取锁。
  2. 如果重试的次数达到了 MAX_SCAN_RETRIES 则改为阻塞锁获取,保证能获取成功。

再结合图看看 put 的流程。

  1. 将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry。
  2. 遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value。
  3. 不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。
  4. 最后会解除在 1 中所获取当前 Segment 的锁。

get 方法

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

 

public V get(Object key) {

     Segment<K,V> s; // manually integrate access methods to reduce overhead

HashEntry<K,V>[] tab;

int h = hash(key);

long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;

if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&

(tab = s.table) != null) {

for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile

(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);

e != null; e = e.next) {

K k;

if ((k = e.key) == key || (e.hash == h && key.equals(k)))

return e.value;

}

}

return null;

}

get 逻辑比较简单:

只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。

由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。

ConcurrentHashMap 的 get 方法是非常高效的,因为整个过程都不需要加锁。

Base 1.8

1.7 已经解决了并发问题,并且能支持 N 个 Segment 这么多次数的并发,但依然存在 HashMap 在 1.7 版本中的问题。

那就是查询遍历链表效率太低。

因此 1.8 做了一些数据结构上的调整。

首先来看下底层的组成结构:

看起来是不是和 1.8 HashMap 结构类似?

其中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。

也将 1.7 中存放数据的 HashEntry 改为 Node,但作用都是相同的。

其中的 val next 都用了 volatile 修饰,保证了可见性。

put 方法

重点来看看 put 函数:

  • 根据 key 计算出 hashcode 。
  • 判断是否需要进行初始化。
  • f 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
  • 如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
  • 如果都不满足,则利用 synchronized 锁写入数据。
  • 如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。

get 方法

  • 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
  • 如果是红黑树那就按照树的方式获取值。
  • 就不满足那就按照链表的方式遍历获取值。

1.8 在 1.7 的数据结构上做了大的改动,采用红黑树之后可以保证查询效率(O(logn)),甚至取消了 ReentrantLock 改为了 synchronized,这样可以看出在新版的 JDK 中对 synchronized 优化是很到位的。

总结

看完了整个 HashMap 和 ConcurrentHashMap 在 1.7 和 1.8 中不同的实现方式相信大家对他们的理解应该会更加到位。

其实这块也是面试的重点内容,通常的套路是:

  1. 谈谈你理解的 HashMap,讲讲其中的 get put 过程。
  2. 1.8 做了什么优化?
  3. 是线程安全的嘛?
  4. 不安全会导致哪些问题?
  5. 如何解决?有没有线程安全的并发容器?
  6. ConcurrentHashMap 是如何实现的? 1.7、1.8 实现有何不同?为什么这么做?

这一串问题相信大家仔细看完都能怼回面试官。

除了面试会问到之外平时的应用其实也蛮多,像之前谈到的 Guava 中 Cache 的实现就是利用 ConcurrentHashMap 的思想。

同时也能学习 JDK 作者大牛们的优化思路以及并发解决方案。

其实写这篇的前提是源于 GitHub 上的一个 Issues,也希望大家能参与进来,共同维护好这个项目。

多线程-ConcurrentHashMap源码详解 概述: HashMap是集合中最常用的数据结构之一,由于HashMap非线程安全,因此不能用于并发访问场景。在jdk1.5之前,通常使用HashTable作为HashMap的线程安全版本。HashMap对读写操作进行全局加锁,在高并发的条件下会造成严重的锁竞争和等待,极大地降低了系统的吞吐量。 优点: 相比于HashTable以及Collections.synchronizedMap(),Conc... 阅读详情

相关推荐

万字长文,图文详解。从HashMap到ConcurrentMap,如何一步步实现线程安全。

定义Map是一个用于存储 Key-Value 键值对的集合类,也就是一组键值对的映射,在 Java 中Map是一个接口,是和Collection接口同一等级的集合根接口;存储结构上图看起来像是数据库中的关系表,有类似的两个字段,KeySet(键的集合)和 Values(值的集合),每一个键值对都是一个Entry;特点没有重复的 key;key 用set保存,所以 key 必须唯一;Map基本上是通过 key 来获取 value,如果有两个相同的 key,计算机将不知道取哪个值,如果put。

2301_76607156的博客 444

HashMap和ConcurrentHashMap

Map这种映射关系的集合,由于它继承了数组和双向链表的优势,适合增删改查,在平时的编程中使用的还是比较多的。所以有必要很清楚的了解map集合在使用时的一些坑。 本文基于常用的jdk版本1.7和1.8来分析下常用的两种类型map集合:hashMap和ConcurrentHashMap。 一、HashMap ...

jdk819的博客 6512

详谈HashMap和ConcurrentHashMap的区别(HashMap的底层源码)

下面小编就为大家带来一篇详谈HashMap和ConcurrentHashMap的区别(HashMap的底层源码)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

HashMap和ConcurrentHashMap的区别,详解HashMap和ConcurrentHashMap数据结构

在讨论哈希表之前,我们先大概了解下其他数据结构在新增,查找等基础操作执行性能数组采用一段连续的存储单元来存储数据。对于指定下标的查找,时间复杂度为O(1);通过给定值进行查找,需要遍历数组,逐一比对给定关键字和数组元素,时间复杂度为O(n),当然,对于有序数组,则可采用二分查找,插值查找,斐波那契查找等方式,可将查找复杂度提高为O(logn);对于一般的插入删除操作,涉及到数组元素的移动,其平均复杂度也为O(n)线性链表。

qq_39350797的博客 4378

ovs实现lb负载均衡

负载均衡器的实现原理是通过硬件或软件设备将客户端访问流量根据转发策略分发到多个服务器或设备上,以确保系统的负载均衡。二层负载均衡‌:使用虚拟MAC地址方式,根据OSI模型的二层进行负载均衡。‌四层负载均衡‌:基于IP+端口的负载均衡,主要分析IP层及TCP/UDP层,实现四层负载均衡。‌七层负载均衡‌:从应用层开始,根据虚拟的URL或IP、主机名接收请求,再转向相应的处理服务器。

bobi的博客 953

C语言入门—初识C语言

一、C语言概述 1、什么是C语言 语言是用来交流沟通的,有一方说,有一方听。必须要两方参与。(自言自语的除外) 语言是人与人交流,而C语言是人与机器交流 语言有独特的语法规则和定义,双方必须遵循这些规则和定义才能实现真正的交流。 接下来首先了解下计算机的组成: 2、计算机介绍 2.1、计算机结构组成 2.2、计算机系统组成 2.3、程序的指令 指令是对计算机进行程序控制的最小单元 计算机的指令系统是指所有指令的集合 程序是指为完成某一项特定任务而用某种言语编写的一组指令序列 3、C语言发展历程 3.

jun319817的博客 1035

HashMap和ConcurrentHashMap源码分析

目录 HashMap 1 HashMap概述 2 涉及到的数据结构:处理hash冲突的链表和红黑树以及位桶 3 HashMap源码分析 类的继承关系 类的属性 构造函数 hash算法 重要方法分析 ConcurrentHashMap 重要的属性 put方法 Remove方法 size方法 HashMap 1 HashMap概述 在JDK1.8之前,HashM...

WM_CARA的博客 1166

医学图像中的小目标病灶检测难题:我的硕士研究总结与思考

医学图像中的小目标检测和分割,主要指在CT、MRI、超声等医学影像中识别和分割微小病灶或细小结构。这些小目标通常体积小、边界模糊、特征不明显,却往往是关键病灶,例如肺部小结节、脑微出血、乳腺微钙化等。

weixin_45638136的博客 2370

HashMap 、ConcurrentHashMap 原理分析

思维导图 未完待续… 《HashMap 、ConcurrentHashMap 原理分析》 参考资料: HashMap Java HashMap原理及内部存储结构 HashMap原理详解,看不懂算我输(附面试题) ConcurrentHashMap 对比源码分析 ConcurrentHashMap 是如何成为一个线程安全的 HashMap 解读Java8中ConcurrentHashMap是如何保证线程安全的 ConcurrentHashMap 原理浅析 Why ConcurrentHashMap c

轻狂書生的博客 386

ADS1115 0-10V高精度ADC模块原理图设计,已量产

在水质监测、智能气象站、环境感知等领域,0-10V 电压输出的传感器占据了重要地位。这类传感器输出信号稳定、抗干扰能力强,但对数据采集模块的精度、线性度和易用性提出了严苛要求。本文将深度解析一款专为 0-10V 传感器设计的高精度 ADC 模块,从硬件架构、性能指标到工程化实践,全面呈现其设计思路与核心价值。

不脱发的程序猿 277

IgH EtherCAT 从入门到精通:第 3 章 第一次运行 Hello EtherCAT

摘要:启动EtherCAT主站实践指南 本章详细介绍了如何配置和启动EtherCAT主站系统,主要内容包括: 配置文件详解:讲解/etc/ethercat.conf中的关键配置项,包括主网卡指定、冗余备份设置和网卡驱动选择。 启动流程:说明通过systemd服务和手动命令两种方式加载内核模块,重点介绍了ec_master和ec_generic模块的加载顺序及验证方法。 命令行工具:列举ethercat工具的常用命令,如查看主站状态、扫描从站设备、读取PDO/SDO数据等。 示例程序:简要介绍内核态示例min

从事嵌入式linux android驱动开发,拥有全栈能力 311

【Java开发日记】我们来讲一讲 ConcurrentHashMap

原来segment里面才是真正的hashtable,即每个segment是一个传统意义上的hashtable,如上图,从两者的结构就可以看出区别,这里就是找出需要的entry在table的哪一个位置,之后得到的entry就是这个链的第一个节点,如果e!这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。

纵有疾风起 人生不言弃 1470

【3天快速入门WPF】8-数据绑定基础

以下是一个简单的 WPF 数据绑定示例,使用两个TextBox控件分别表示Name和Age来进行进行数据绑定试验。运行程序后,你会看到初始界面中TextBox 中显示初始名字 “张三”,TextBox 中显示初始年龄 “25”。在 XAML 中创建一个简单的界面,包含一个 TextBox 用于输入名字,一个 TextBox 用于显示年龄。当你在 TextBox 中修改名字时,Person 对象的 Name 属性会被自动更新,反之亦然。它的主要作用是为控件及其子控件提供一个默认的数据源,用于数据绑定。

Zy100Papa的博客 969

ConcurrentHashMap实现原理及源码分析

本文转载自:https://www.cnblogs.com/chengxiao/p/6842045.html ConcurrentHashMap实现原理及源码分析   ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对HashMap的实现原理还不甚了解,可参考我的另一篇文章HashMap实现原理及源码分析...

luoyunhui123456的博客 240

数仓建模—数据治理框架选型

全名主要亮点覆盖面最广:从数据治理、架构、质量、安全到元数据、主数据、数据仓库等等,几乎所有数据管理主题都能在DMBOK里找到相应章节。概念权威统一:帮助组织建立通用术语和知识基础,减少“部门之间说法不一致”的沟通成本。适用场景:如果你所在公司(或团队)正准备系统梳理数据管理的方方面面,DMBOK是一份非常全面的指导手册,尤其适合培养新人或为整个组织统一认知。需要注意:DMBOK偏“知识指引”,不直接提供成熟度模型或实施路线图。想要落地,还需要结合其他评估模型或项目管理方法。小案例举例:某。

2401_84052244的博客 4744

LangGraph 实战教程:搭建一个智能办公多Agent协作平台

只存储必要的信息:不要把所有的原始数据都存储在状态中,比如销售数据可以存储在数据库或向量数据库中,状态中只存储数据的 ID 或摘要。使用类型提示:避免类型错误,提高代码的可维护性。支持增量更新:LangGraph 默认支持状态的增量更新(通过reduce函数),比如messages列表可以通过追加新消息的方式更新,而不是每次都重新创建整个列表。Normal Edge 的定义非常简单,只需要在StateGraph对象(稍后会详细讲解)上调用add_edge# 创建一个 StateGraph 对象。

SuperAGI2025 221

Java集合---ConcurrentHashMap原理分析

文章目录背景线程不安全的HashMap效率低下的HashTable容器锁分段技术应用场景源码解读不变(Immutable)和易变(Volatile)其它定位操作数据结构删除操作remove(key)get操作put操作size()操作 背景 线程不安全的HashMap 因为多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMa...

记录日常的点点滴滴 2728

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

ConcurrentHashMap 底层原理

这篇文章,我打算从以下几个方面来讲。 1)多线程下的 HashMap 有什么问题? 2)怎样保证线程安全,为什么选用 ConcurrentHashMap? 3)ConcurrentHashMap 1.7 源码解析 底层存储结构 常用变量 构造函数 put() 方法 ensureSegment() 方法 scanAndLockForPut() 方法 rehash() 扩容机制 get() 获取元素方法 remove()

阳阳的博客 2801

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

hashmap实现原理(哈希值计算,put方法,扩容) jdk1.8带来的优化 hashmap并发安全 ConcurrentHashMap

                HashMap的源码,实现原理,JDK8中对HashMap做了怎样的优化。 ArrayList和LinkedList的优缺点——数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。 hashmap底层是由数组加链表构成,将需要存放的元素算出hash值,在对数组长度取余,得到下标。因为取余得到的值,可能相同。于是该...

qq_35240226的博客 823

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析

今天发一篇"水文",可能很多读者都会表示不理解,不过我想把它作为并发序列文章中不可缺少的一块来介绍。本来以为花不了多少时间的,不过最终还是投入了挺多时间来完成这篇文章的。 网上关于 HashMap 和 ConcurrentHashMap 的文章确实不少,不过缺斤少两的文章比较多,所以才想自己也写一篇,把细节说清楚说透,尤其像 Java8 中的 ConcurrentHashMap,大部分文章都说不...

达文西的博客 207

ConcurrentHashMap万字长文,太全面了!

点击上方“码农突围”,马上关注这里是码农充电第一站,回复“666”,获取一份专属大礼包真爱,请设置“星标”或点个“在看”来源:^_TONY_^cnblogs.com/ITtangtang...

欢迎关注公众号:【码农突围】,回复9999,可以获取一份LeetCode刷题笔记 301

HashTable、HashMap、ConcurrentHashMap区别详解

HashTable、HashMap、ConcurrentHashMap是面试的时候面试官经常问道的知识点,今天分别复习一下他们的源码与区别。 一、HashMap         HashMap是基于hash表实现的,不支持并发操作,数据结构也就是采用数组+链表的形式实现的,其实每一个数组元素也就是一个Entry类型的单链表,下面通过一张图了解HashMap结构。        HashM...

不忘初心的博客 266

面试再被问到 ConcurrentHashMap,把这篇文章甩给他!

扫描下方海报试读本文来源:https://www.cnblogs.com/ITtangtang/p/3948786.html一、背景:线程不安全的HashMap因为多...

石杉的架构笔记 387

电竞管理系统架构设计与SpringBoot微服务实践

微服务架构通过将系统拆分为独立部署的服务单元,显著提升了复杂业务系统的可维护性和扩展性。基于SpringBoot的快速开发特性,配合Spring Cloud生态,开发者可以高效构建分布式系统。在电竞赛事管理场景中,采用赛事服务、赞助服务、用户服务的三模块设计,通过RESTful API实现服务间通信,关键接口响应时间控制在200ms内。系统运用TF-IDF算法实现智能赞助匹配,使赞助达成率提升45%,同时采用三级缓存架构(Caffeine+Redis+MySQL读写分离)保障高并发访问性能。这种技术方案有效

weixin_34377919的博客 413

Java-Concurrent框架--ConcurrentHashMap源码解析(JDK1.7)

在Map的实现类中除了HashMap和HashTable以外还有一个ConcurrentHashMap。HashMap和HashTable一都非常熟悉,HashTable是线程安全的、且不能存储Null值,HashMap是非线程安全的、可以存储Null值。

Monkey__z的博客 1227
上一篇: Spring MVC+Spring+Mybatis实现支付宝支付功能(图文详解)
下一篇: Centos7+Nginx+Tomcat搭建集群环境
木木那年
博客等级 码龄12年 37粉丝 36原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值