结合底层源码介绍ConcurrentHashMap如何保证线程安全,佬会爱上这篇文章嘛

这篇具有很好参考价值的文章主要介绍了结合底层源码介绍ConcurrentHashMap如何保证线程安全,佬会爱上这篇文章嘛。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:
本篇文章主要讲解结合底层源码介绍ConcurrentHashMap如何保证线程安全的知识。该专栏比较适合刚入坑Java的小白以及准备秋招的大佬阅读。

如果文章有什么需要改进的地方欢迎大佬提出,对大佬有帮助希望可以支持下哦~

小威在此先感谢各位小伙伴儿了😁

结合底层源码介绍ConcurrentHashMap如何保证线程安全,佬会爱上这篇文章嘛

以下正文开始

结合底层源码介绍ConcurrentHashMap如何保证线程安全,佬会爱上这篇文章嘛

JDK1.7保证线程安全

ConcurrentHashMap在JDK 1.7和JDK 1.8版本保证线程安全及其底层数据结构是不一样的,这一块是面试中的重点,接下来详细介绍一下它们。

在JDK 1.7中,ConcurrentHashMap采用了分段锁(Segment)的设计来保证线程安全。下面我们将通过详细解读其底层源码,来介绍其线程安全实现原理。

ConcurrentHashMap的主要类是Segment。每个Segment是一个独立的锁,并且维护着一个HashEntry数组。HashEntry是链表节点,存储了键值对。

首先,我们来看一下ConcurrentHashMap的基本数据结构:

static final class HashEntry<K, V> {
    final int hash;
    final K key;
    volatile V value;
    volatile HashEntry<K, V> next;

    HashEntry(int hash, K key, V value, HashEntry<K, V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
}

static final class Segment<K, V> extends ReentrantLock implements Serializable {
    static final float LOAD_FACTOR = 0.75f;

    transient volatile HashEntry<K, V>[] table;
    transient int count;
    transient int modCount;
    transient int threshold;
    final float loadFactor;
}

每个Segment都是一个继承自ReentrantLock的可重入锁,具备独立的线程安全性。table是Segment内部的HashEntry数组,用于存储键值对。count表示当前Segment中的元素数量,modCount用于记录修改次数,threshold表示扩容的阈值,loadFactor表示加载因子。

接下来,我们看一下ConcurrentHashMap的put操作:

public V put(K key, V value) {
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key.hashCode());
    int segmentIndex = getSegmentIndex(hash);
    return segments[segmentIndex].put(key, hash, value, false);
}

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    lock(); // 获取当前Segment的锁
    try {
        int c = count;
        if (c++ > threshold) // 判断是否需要扩容
            rehash();
        HashEntry<K, V>[] tab = table;
        int index = hash & (tab.length - 1);
        HashEntry<K, V> first = tab[index];
        HashEntry<K, V> e = first;

        while (e != null && (e.hash != hash || !key.equals(e.key))) 
            e = e.next;

        V oldValue;
        if (e != null) { // 键存在,更新值
            oldValue = e.value;
            if (!onlyIfAbsent)
                e.value = value;
        } else { // 键不存在,创建新节点并添加到链表头部
            oldValue = null;
            ++modCount;
            tab[index] = new HashEntry<K, V>(hash, key, value, first);
            count = c; // 更新元素数量
        }
        return oldValue;
    } finally {
        unlock(); // 释放当前Segment的锁
    }
}

在put操作中,首先通过hash函数计算键的散列值hash,然后根据散列值获取对应的Segment。接着,通过Segment的锁保证了当前操作的线程安全

在获取到Segment的锁之后,首先判断当前Segment中的元素数量count是否超过了阈值threshold,如果超过了则进行扩容。然后通过散列值和数组长度计算出键对应的索引位置index,并从对应的链表开始遍历,寻找是否存在相同的键。

如果找到了相同的键,则更新对应的值;如果没有找到相同的键,则创建一个新的HashEntry节点,并将其添加到链表的头部。

在完成操作后,释放Segment的锁。

通过分段锁的设计,JDK 1.7的ConcurrentHashMap允许多个线程同时操作不同的Segment,从而提高了并发性能。虽然在高并发情况下仍可能存在竞争问题,但通过细粒度的锁设计,可以减少锁竞争的概率,提升整体性能。
结合底层源码介绍ConcurrentHashMap如何保证线程安全,佬会爱上这篇文章嘛

JDK1.8保证线程安全

在JDK 1.8中,ConcurrentHashMap进行了重大改进,采用了更加高效的并发控制机制来保证线程安全。相较于JDK 1.7的分段锁设计,JDK 1.8引入了基于CAS(Compare and Swap)操作和链表/红黑树结构的锁机制以及其他优化,大大提高了并发性能

底层数据结构:
JDK 1.8中的ConcurrentHashMap采用了数组+链表/红黑树的结构。具体来说,它将整个哈希桶(Hash Bucket)划分为若干个节点(Node)。每个节点代表一个存储键值对的单元,可以是链表节点(普通节点)或红黑树节点(树节点),这取决于节点内的键值对数量是否达到阈值。使用红黑树结构可以提高查找、插入、删除等操作的效率

主要类和数据结构如下:

static final class Node<K, V> implements Map.Entry<K, V> {
    final int hash;
    final K key;
    volatile V value;
    volatile Node<K, V> next;

    Node(int hash, K key, V value, Node<K, V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
}

static final class TreeNode<K, V> extends Node<K, V> {
    TreeNode(int hash, K key, V value, Node<K, V> next) {
        super(hash, key, value, next);
    }
    // 省略了红黑树相关的操作代码
}

static final class ConcurrentHashMap<K, V> {
    transient volatile Node<K, V>[] table;
    transient volatile int sizeCtl;
    transient volatile int baseCount;
    transient volatile int modCount;
}

ConcurrentHashMap的线程安全实现原理:

初始状态:在初始状态下,table为null,sizeCtl为0。当第一个元素被插入时,会根据并发级别(Concurrency Level)计算出数组的长度,并使用CAS操作将数组初始化为对应长度的桶

插入操作

put方法:当进行插入操作时,ConcurrentHashMap首先计算键的散列值,然后根据散列值和数组长度计算出对应的桶位置。接着使用CAS操作尝试插入新节点,如果成功则插入完成;如果失败,则进入下一步。

resize方法:插入节点时,若发现链表中的节点数量已经达到阈值(默认为8),则将链表转化为红黑树,提高查找、插入、删除等操作的效率。在转化过程中,利用synchronized锁住链表或红黑树所在的桶,并进行相应的操作。

forwardTable方法:若节点数量超过阈值(默认为64)且table未被初始化,则使用CAS操作将table指向扩容后的桶数组,并根据需要将链表或红黑树进行分割,以减小线程之间的冲突。

查询操作

get方法:当进行查询操作时,首先计算键的散列值,然后根据散列值和数组长度计算出对应的桶位置。接着从桶位置的链表或红黑树中查找对应的节点。

其他操作

remove方法:当进行删除操作时,首先计算键的散列值,然后根据散列值和数组长度计算出对应的桶位置。接着使用synchronized锁住桶,并进行相应的操作。

综上所述,JDK 1.8的ConcurrentHashMap通过CAS操作、锁机制(synchronized)以及链表/红黑树结构来保证线程安全。CAS操作用于插入新节点和初始化桶数组,锁机制用于链表/红黑树的转化和删除操作,链表/红黑树结构用于提高查找、插入、删除操作的效率。这些优化措施使得ConcurrentHashMap在高并发环境下具有较好的性能表现。

结合底层源码介绍ConcurrentHashMap如何保证线程安全,佬会爱上这篇文章嘛

JDK1.7和JDK1.8对比总结

在JDK 1.7和JDK 1.8中,ConcurrentHashMap有以下主要区别:

JDK 1.7中的实现方式:

  • JDK 1.7中的ConcurrentHashMap使用分段锁(Segment Locking)的设计。它将整个哈希表分成多个段(Segment),每个段都有自己的锁。这样可以降低并发操作时锁的争用范围,提高并发性能。
  • 每个段中包含一个HashEntry数组,每个HashEntry是一个链表结构,用于解决哈希冲突。
  • 由于每个段都有自己的锁,不同的线程可以同时访问不同的段,从而提高了并发度

JDK 1.8中的改进:

JDK 1.8中的ConcurrentHashMap采用了CAS操作、锁机制以及链表/红黑树结构的改进。

  • 数据结构改进:JDK 1.8中使用数组+链表/红黑树的结构,代替了JDK 1.7中的段+链表结构。数组用于存储桶,链表/红黑树用于解决哈希冲突。
  • CAS操作:JDK 1.8使用CAS(Compare and Swap)操作来插入新节点和初始化桶数组。CAS操作是一种乐观锁机制,通过原子操作比较并交换的方式进行,并发安全性更好。
  • 锁的改进:JDK 1.8中引入了基于CAS操作和链表/红黑树结构的锁机制。对于链表/红黑树上的操作,使用synchronized锁住桶,以保证操作的原子性。
  • 链表转化为红黑树:JDK 1.8在插入操作时,当链表中的节点数量达到一定阈值时,会将链表转化为红黑树,提高查找、插入、删除等操作的效率。
  • resize操作的改进:JDK 1.8中的resize操作(扩容)采用了分割链表/红黑树的方式,减小了线程冲突的概率。

总的来说,JDK 1.8中的ConcurrentHashMap在数据结构、CAS操作、锁机制和链表/红黑树结构等方面进行了改进,相较于JDK 1.7,性能更好且并发度更高。这些改进使得JDK 1.8中的ConcurrentHashMap在高并发环境下表现更优秀。

文章到这里就先结束了,感兴趣的可以订阅专栏哈,后续会继续分享相关的知识点。

结合底层源码介绍ConcurrentHashMap如何保证线程安全,佬会爱上这篇文章嘛文章来源地址https://www.toymoban.com/news/detail-505037.html

到了这里,关于结合底层源码介绍ConcurrentHashMap如何保证线程安全,佬会爱上这篇文章嘛的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 源码分析——ConcurrentHashMap源码+底层数据结构分析

    1. 存储结构 Java 7 中 ConcurrentHashMap 的存储结构如上图,ConcurrnetHashMap 由很多个 Segment 组合,而每一个 Segment 是一个类似于 HashMap 的结构,所以每一个 HashMap 的内部可以进行扩容。但是 Segment 的个数一旦 初始化就不能改变 ,默认 Segment 的个数是 16 个,你也可以认为 ConcurrentH

    2024年02月13日
    浏览(52)
  • ConcurrentHashMap 底层原理

    tip:作为程序员一定学习编程之道,一定要对代码的编写有追求,不能实现就完事了。我们应该让自己写的代码更加优雅,即使这会费时费力。 推荐:体系化学习Java(Java面试专题) ConcurrentHashMap 是线程安全的哈希表,它是 Java 并发包中提供的一种高效的并发 Map 实现。Con

    2024年02月14日
    浏览(32)
  • 【Java 基础】ConcurrentHashMap 底层原理

    tip:作为程序员一定学习编程之道,一定要对代码的编写有追求,不能实现就完事了。我们应该让自己写的代码更加优雅,即使这会费时费力。 推荐:体系化学习Java(Java面试专题) ConcurrentHashMap 是线程安全的哈希表,它是 Java 并发包中提供的一种高效的并发 Map 实现。Con

    2024年02月11日
    浏览(35)
  • 线程池ThreadPoolExecutor底层原理源码分析

    ThreadPoolExecutor中提供了两种执行任务的方法: void execute(Runnable command) Future? submit(Runnable task) 实际上submit中最终还是调用的execute()方法,只不过会返回⼀个Future对象,用来获取任务执行结果: execute(Runnable command)方法执行时会分为三步: 注意:提交⼀个Runnable时,不管当前线程

    2024年02月06日
    浏览(64)
  • 【多线程】Java如何实现多线程?如何保证线程安全?如何自定义线程池?

    个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ 线程 : 线程是操作系统能够进行运算调度的最小单位。它被包含在进程中,是进程中的实际运作单位。 生

    2024年02月08日
    浏览(59)
  • Hashmap如何保证线程安全

    本文介绍Java中的HashMap如何进行线程安全的操作、为什么HashMap不是线程安全的。 线程安全Map的三种方法 方法 示例 原理 性能 HashTable MapString, Object map = new Hashtable(); synchronized修饰get/put方法。方法级阻塞,只能同时一个线程操作get或put 很差。 Collections.synchronizedMap MapString, Obj

    2023年04月16日
    浏览(52)
  • Swift如何保证线程安全

    Swift可以通过以下几种方式来保证线程安全 使用互斥锁(Mutex):使用互斥锁可以防止多个线程同时访问共享数据,保证线程安全。 使用OSAtomic操作:OSAtomic操作可以在多线程环境中安全地执行原子操作。 使用DispatchQueue:DispatchQueue可以使用GCD技术实现线程安全。您可以使用

    2024年02月13日
    浏览(39)
  • JUC编程之——线程的start方法及底层源码

    简单的写一个线程的小栗子: 查看java start()方法的源码: 更加底层的C++代码: thread.c (代码位置:openJDKsrcsharenativejavalang) jvm.cpp (代码位置:openIDKhotspotsrcsharevmprims) thread.cpp (代码位置:openJDKhotspotsrcsharevmruntime) 代码——OpenJDK源码网址:http://openjdk.java.net/ 2.3.1 thre

    2023年04月13日
    浏览(40)
  • ArrayList为什么不是线程安全的,如何保证线程安全?

    官方曰, 线程安全就是多线程访问时,采⽤了加锁机制,当⼀个线程访问该类的某个数据时,进⾏保护,其他线程不能进⾏访问直到该线程读取完,其他线程才可使⽤。不会出现数据不⼀致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数

    2024年02月07日
    浏览(61)
  • 如何保证三个线程按顺序执行?不会我教你

    👨‍🎓作者:bug菌 ✏️博客:CSDN、掘金、infoQ、51CTO等 🎉简介:CSDN|阿里云|华为云|51CTO等社区博客专家,历届博客之星Top30,掘金年度人气作者Top40,51CTO年度博主Top12,掘金 | InfoQ | 51CTO等社区优质创作者,全网粉丝合计 15w+   ;硬核微信公众号「猿圈奇妙屋」,欢迎你的

    2024年02月07日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包