分析ConcurrentHashMap源码

JDK1.8版本HashMap源码原理分析 HashMap是一个KV的容器对象,也是日常在开发当中非常常用的对象,比如我们可能经常做一些内存缓存的时候,很多时候就选用HashMap这种KV的数据结构。在实现原理上,是基于哈希表的Map接口实现,是常用的java集合之一,非线程安全的。 HashMap可以存储null的key和value,但是null作为键值只能有一个,做为值的话,可以是多个。这和Map的键要保持唯一性并不冲突。 一、HashMap的类图结构 二、概念、原理概述 Jdk1.8之前的HashMap是由数组+链表作为底层数据结构实现的,数 阅读详情

ConcurrentHashMap 是Java并发包中提供的一个线程安全且高效的HashMap实现

HashMap的缺点:

多线程环境下HashMap会有线程安全问题,扩容可能会造成环形链表,使cpu空转达到100%,但是HashTable可以保证线程安全

HashTable缺点:

底层使用synchronized锁保证线程安全问题,但是将整个数组锁住了,最终只有一个线程能够调用get或者是put方法,如果没有获取锁的线程就会变为阻塞状态,效率非常低。

多线程环境下建议使用ConcurrentHashMap集合

ConcurrentHashMap1.7源码解读

ConcurrentHashMap相当于把一个大的ConcurrentHashMap集合拆分成16个HashTable类,每次存放先要计算存放在哪个HashTable里面,然后还要计算存放在HashTable里面的哪个HashEntry<K,V>里面,相当于把锁的粒度进行拆分了,把大锁拆分成小锁。

核心构造参数分析

//初始的容量private static final int DEFAULT_CAPACITY = 16;//最大容量private static final int MAXIMUM_CAPACITY = 1 << 30;//HashEntry table的扩容因子private static final float LOAD_FACTOR = 0.75f;// 默认的并发度private static final int DEFAULT_CONCURRENCY_LEVEL = 16;//最小的segment数量static final int MIN_SEGMENT_TABLE_CAPACITY = 2;//最大的segment数量static final int MAX_SEGMENTS = 1 << 16; 

初始化ConcurrentHashMap

public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {     //验证参数    if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)         throw new IllegalArgumentException();     //并发级别大于并发值的话    if (concurrencyLevel > MAX_SEGMENTS)         //最大为        concurrencyLevel = MAX_SEGMENTS;     //sshift=ssize平方的次 =4    int sshift = 0;     //ssize=segments数组的容量=16    int ssize = 1;     while (ssize < concurrencyLevel) {         ++sshift;         ssize <<= 1;     }     segmentShift = 32 - sshift;  //32-4=28 计算index    segmentMask = ssize - 1;  //16-1=15  与运算的时候均匀的存放到Segment对象中    this.segments = Segment.newArray(ssize);     //数组容量最大不能大于2的三十次幂    if (initialCapacity > MAXIMUM_CAPACITY)         initialCapacity = MAXIMUM_CAPACITY;     //HashEntry 的初始容量  16/16=1    int c = initialCapacity / ssize;     //如果1*16<16    if (c * ssize < initialCapacity)         ++c;     //HashEntry table默认大小为2    int cap = MIN_SEGMENT_TABLE_CAPACITY;     while (cap < c)         cap <<= 1;  //创建第一个Segment,并放入Segment[]数组中,作为第一个Segment        Segment<K,V> s0 =new Segment<K,V>(loadFactor, (int)(cap * loadFactor),(HashEntry<K,V>[])new HashEntry[cap]);       //初始化16大小的Segment        Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];       //利用cas把s0放入ss中  ss[0]=s0        UNSAFE.putOrderedObject(ss, SBASE, s0);         //赋值给全局segments        this.segments = ss;}

这里在构造函数初始化S0,目的就是方便其他的key落到不同的Segment中,能够知道创建的Segment的参数,直接采用这个参数

put方法

    public V put(K key, V value) {        Segment<K,V> s;        //参数判断        if (value == null)            throw new NullPointerException();        //这里对key求hash值,并确定应该放到segment数组的索引位置        int hash = hash(key);        //j为索引位置 segmentShift=28  segmentMask=15  hash >>> segmentShift保留最高位4位        int j = (hash >>> segmentShift) & segmentMask;        //判断索引下是否有Segment对象,没有帮忙创建 s=Segment[j]=null        if ((s = (Segment<K,V>)UNSAFE.getObject(segments, (j << SSHIFT) + SBASE)) == null)             //创建一个Segment对象            s = ensureSegment(j);        //这里很关键,找到了对应的Segment,则把元素放到Segment中去        return s.put(key, hash, value, false);    }

根据key的hash值,在Segment数组中找到相应的位置,如果相应位置的Segment还未初始化,则通过CAS进行赋值,接着执行Segment对象的put方法通过加锁机制插入数据

初始化segment的方法

private Segment<K,V> ensureSegment(int k) {//拿到Segments[]数组final Segment<K,V>[] ss = this.segments;//获取k所在的segment在内存中的偏移量long u = (k << SSHIFT) + SBASE; // raw offsetSegment<K,V> seg;//获取k所在的segmen 判断k所在的segmen是否为nullif ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {//初始化一个segment,直接使用Segments[0]的属性,这就是为什么在构造方法中初始化Segments[0]的原因//Segments[0]在初始化ConcurrentHashMap时,已经初始化了一个segment放到Segments[0]Segment<K,V> proto = ss[0]; // use segment 0 as prototype//然后就是获取Segments[0]中HashEntry数组的数据int cap = proto.table.length;float lf = proto.loadFactor;int threshold = (int)(cap * lf);//初始化一个HashEntry数组,大小和Segments[0]中的HashEntry一样。HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];//第二次获取k所在的segment(防止其他线程已经初始化好)if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) { // recheck//如果还是null,创建一个segment并通过cas设置到对应的位置Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);//第三次获取k所在的segmen,判断segmen是否为null,自旋CAS来创建对应Segmentwhile ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))== null) {    // CASif (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))break;}}}return seg;}

这里有三个CAS查询

第一个判断==null,获取s0默认参数信息、加载因子、初始容量,tab = (HashEntry<K,V>[])new HashEntry[cap]

第二个判断==null, new Segment<K,V>(lf, threshold, tab)

第三个判断==null,把s赋值给seg,整个方法就是相当于创建一个Segment<K,V>

自旋CAS来创建对应Segment,即使不加锁也能保障线程安全

Segment的put方法

final V put(K key, int hash, V value, boolean onlyIfAbsent) {    //先尝试对segment加锁,如果直接加锁成功,那么node=null;如果加锁失败,则会调用scanAndLockForPut方法去获取锁,    //在这个方法中,获取锁后会返回对应HashEntry(要么原来就有要么新建一个) scanAndLockForPut自旋的方式再次尝试获取锁     HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);    V oldValue;    try {        //这里是一个优化点,由于table自身是被volatile修饰的,然而put这一块代码本身是加锁了的,所以同一时间内只会有一个线程操作这部分内容,        //所以不再需要对这一块内的变量做任何volatile修饰,因为变量加了volatile修饰后,变量无法进行编译优化等,会对性能有一定的影响        //故将table赋值给put方法中的一个局部变量,从而使得能够减少volatile带来的不必要消耗。                HashEntry<K,V>[] tab = table;        //计算当前元素所在HashEntry的index        int index = (tab.length - 1) & hash;        //先获取需要put的<k,v>对在当前这个segment中对应的链表的表头结点。        HashEntry<K,V> first = entryAt(tab, index);        //开始遍历first为头结点的链表        for (HashEntry<K,V> e = first;;) {             //e不为空,说明当前键值对需要存储的位置有hash冲突            if (e != null) {                //依据onlyIfAbsent来判断是否覆盖已有的value值                K k;                //说明键的Hash值一样,内容也一样                if ((k = e.key) == key ||(e.hash == hash && key.equals(k))) {                     //key相同,那么直接用新的元素覆盖旧的元素                    oldValue = e.value;                    if (!onlyIfAbsent) {                        e.value = value;                        ++modCount;                    }                    break;                }                //未进入上面的if条件中,说明当前e节点对应的key不是需要的,直接遍历下一个节点。                e = e.next;            }            else {//<2> 头插法                //进入到这个else分支,说明e为空,对应有两种情况下e可能会为空,即:                // 1>. <1>中进行循环遍历,遍历到了链表的表尾仍然没有满足条件的节点。                // 2>. e=first一开始就是null(可以理解为即一开始就遍历到了尾节点)                                if (node != null) //这里有可能获取到锁是通过scanAndLockForPut方法内自旋获取到的,这种情况下依据找好或者说是新建好了对应节点,node不为空                    node.setNext(first);                // 当然也有可能是这里直接第一次tryLock就获取到了锁,从而node没有分配对应节点,即需要给依据插入的k,v来创建一个新节点                else                         node = new HashEntry<K,V>(hash, key, value, first);                int c = count + 1; //总数+1 在这里依据获取到了锁,即是线程安全的!对应了上述对count变量的使用规范说明。                if (c > threshold && tab.length < MAXIMUM_CAPACITY)//判断是否需要进行扩容                                    //扩容是直接重新new一个新的HashEntry数组,这个数组的容量是老数组的两倍,                    //新数组创建好后再依次将老的table中的HashEntry插入新数组中,所以这个过程是十分费时的,应尽量避免。                    //扩容完毕后,还会将这个node插入到新的数组中。                                        rehash(node);                else                    //数组无需扩容,那么就直接插入node到指定index位置,这个方法里用的是UNSAFE.putOrderedObject                    //网上查阅到的资料关于使用这个方法的原因都是说因为它使用的是StoreStore屏障,而不是十分耗时的StoreLoad屏障                    //给我个人感觉就是putObjectVolatile是对写入对象的写入赋予了volatile语义,但是代价是用了StoreLoad屏障                    //而putOrderedObject则是使用了StoreStore屏障保证了写入顺序的禁止重排序,但是未实现volatile语义导致更新后的不可见性,                    //当然这里由于是加锁了,所以在释放锁前会将所有变化从线程自身的工作内存更新到主存中。                    //这一块对于putOrderedObject和putObjectVolatile的区别有点混乱,不是完全理解,网上也没找到详细解答,查看了C源码也是不大确定。                    //希望有理解的人看到能指点一下,后续如果弄明白了再更新这一块。                    setEntryAt(tab, index, node);                ++modCount;                count = c;                oldValue = null;                break;            }        }    } finally {        unlock();    }    return oldValue;}

多线程下put方法获取锁失败,就会使用scanAndLockForPut遍历获取锁然后进行数据插入

private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {//获取k所在的segment中的HashEntry的头节点(segment中放得是HashEntry数组,HashEntry又是个链表结构)HashEntry<K,V> first = entryForHash(this, hash);HashEntry<K,V> e = first;//缓存需要插入的数据HashEntry<K,V> node = null;int retries = -1; // negative while locating nodewhile (!tryLock()) {//自旋尝试获取k所在segment的锁HashEntry<K,V> f; // to recheck first belowif (retries < 0) {if (e == null) {//所在HashEntry链表不存在,则根据传过来的key-value创建一个HashEntryif (node == null) node = new HashEntry<K,V>(hash, key, value, null);retries = 0;} else if (key.equals(e.key))//找到要放得值,则设置segment重试次数为0retries = 0;else //从头节点往下寻找key对应的HashEntrye = e.next;} else if (++retries > MAX_SCAN_RETRIES) {//超过最大重试次数就将当前操作放入到Lock的队列中 阻塞了lock();break;        //retries只有0和1 如果发现头节点有变化,就重新获取HashEntry链表的头结点} else if ((retries & 1) == 0 &&(f = entryForHash(this, hash)) != first) {e = first = f; // re-traverse if entry changedretries = -1;}}return node;}

trylock尝试获取锁不会阻塞,lock获取锁不成功会阻塞,先缓存了当前冲突的链表,然后每次检查当前链表头节点是否有变化,如果有变化说明被修改过,因为采用的是头插法。在发生变化的情况下,修改为最新的。

通过MAX_SCAN_RETRIES控制自旋次数最大为64,防止无限制的重复自旋浪费资源。

底层实现原理:

由多个不同的Segment对象组成,lock锁锁住,用Unsafe类查询主内存数据,使用cas做修改

假如有16个线程均匀分布在每个Segment上,往里面添加数据,锁没有产生竞争,那么效率是没有任何影响的。但如果有三个线程同时都获取到同一个Segment中,那么这三个key都会上锁,JDK1.7里面采用乐观锁lock保证线程安全。

ConcurrentHashMap1.8源码解读

//默认为0  -1标示其他线程已经在扩容了  -2标示当前有两个线程在扩容private transient volatile int sizeCtl;

put方法

final V putVal(K key, V value, boolean onlyIfAbsent) {        //key不允许为空        if (key == null || value == null) throw new NullPointerException();        int hash = spread(key.hashCode());        //记录链表长度 大于8的情况下转为红黑树        int binCount = 0;        //死循环  相当于自旋  table是全局可见变量        for (Node<K,V>[] tab = table;;) {            Node<K,V> f;             int n, i, fh;            //第一次循环如果tab为空就做初始化              if (tab == null || (n = tab.length) == 0)                tab = initTable();            //第二次循环tab不为空  计算key所在的index 查找该node节点 如果没有发生index冲突            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {                //CAS 在当前位置赋值一个node节点                if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))                    break;                   // no lock when adding to empty bin            }            //其他线程在扩容,参与一起扩容            else if ((fh = f.hash) == MOVED)                tab = helpTransfer(tab, f);            //index节点发生冲突之后            else {                V oldVal = null;                //给当前index位的node节点加锁                synchronized (f) {                    //再次赋值判断 如果是当前缓存的节点                    if (tabAt(tab, i) == f) {                        if (fh >= 0) {                            binCount = 1;                            for (Node<K,V> e = f;; ++binCount) {                                K ek;                                //key值比较相等就修改覆盖                                if (e.hash == hash &&((ek = e.key) == key ||(ek != null&& key.equals(ek)))) {                                    oldVal = e.val;                                    if (!onlyIfAbsent)                                        e.val = value;                                    break;                                }                                //不相等的话 追加在链表后面                                Node<K,V> pred = e;                                if ((e = e.next) == null) {                                    pred.next = new Node<K,V>(hash, key,                                                              value, null);                                    break;                                }                            }                        }                        //判断是否为红黑树                        else if (f instanceof TreeBin) {                            Node<K,V> p;                            binCount = 2;                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,                                                           value)) != null) {                                oldVal = p.val;                                if (!onlyIfAbsent)                                    p.val = value;                            }                        }                    }                }                //                if (binCount != 0) {                    //binCount>8 转换为红黑树                    if (binCount >= TREEIFY_THRESHOLD)                        treeifyBin(tab, i);                    if (oldVal != null)                        return oldVal;                    break;                }            }        }       //对每个线程里面保存的size进行求和 算出整个集合的元素        addCount(1L, binCount);        return null;    }

加锁分为了两种:

1、没有发生hash冲突的时候,如果添加的元素的位置在数组中是空的话,那么就使用CAS的方式来加入元素,这里加锁的粒度是数组中的元素

2、如果出现了hash冲突,添加的元素的位置在数组中已经有了值,使用的synchronized,那么又存在三种情况
(1)key相同,那么直接用新的元素覆盖旧的元素
(2)如果数组中的元素是链表的形式,那么将新的元素挂载在链表尾部
(3)如果数组中的元素是红黑树的形式,那么将新的元素加入到红黑树

锁住的对象就是数组中的node元素,加锁的粒度和第一种情况相同

初始化Table

    private final Node<K,V>[] initTable() {        Node<K,V>[] tab; int sc;        //tab为空的时候          while ((tab = table) == null || tab.length == 0) {            //sizeCtl如果小于0表示已经有其他线程在做扩容了  当前如果只有一个线程那么sizeCtl=0            if ((sc = sizeCtl) < 0)                //当前线程就释放cpu执行权  如果线程被唤醒那么tab肯定不为空                Thread.yield();             //CAS修改SIZECTL为-1            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {                try {                    //如果当前tab为空的情况下                    if ((tab = table) == null || tab.length == 0) {                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;                        @SuppressWarnings("unchecked")                        //对node初始化长度默认16                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];                        table = tab = nt;                        //n=16  16>>>2=4  sc=12  扩容大小                        sc = n - (n >>> 2);                    }                } finally {                    sizeCtl = sc;                }                break;            }        }        //初始化完毕        return tab;    }

只有一个线程情况下就会把SIZECTL改为-1,然后赋值table结束循环返回数据,但是16个线程进来同时,只有一个能够进入到tab为空的方法判断,SIZECTL为0,这时候剩下15个一直在自旋等待SIZECTL修改为-1,假如第一个线程还没执行完就挂机,就会导致剩下的一直进行自旋,使CPU飙升。

面试题

ConcurrentHashMap1.7底层实现原理?

ReentrantLock+Segment+HashEntry

一个 ConcurrentHashMap 里包含一个 Segment 数组。一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。

ConcurrentHashMap1.7 put操作原理?

put操作时需要计算两次hash索引,先计算所在Segment的下标,如果该Segment还没有初始化,即通过CAS操作进行赋值,然后计算在该Segment中HashEntry 的下标,ReentrantLock的tryLock()方法尝试去获取锁,如果获取成功就直接插入相应的位置,使用尾插法。如果Segment被其他线程占有,当前线程会以自旋的方式继续尝试获取锁知道达到最大重试次数就挂起等待唤醒。

ConcurrentHashMap1.7是怎样实现扩容的?

ConcurrentHashMap 的扩容是仅仅和每个Segment元素中HashEntry数组的长度有关,但需要扩容时,只扩容当前Segment中HashEntry数组即可。也就是说ConcurrentHashMap中Segment[]数组的长度是在初始化的时候就确定了默认16,后面扩容不会改变这个长度。

ConcurrentHashMap 的并发度是什么?

程序运行时能够同时更新 ConccurentHashMap且不产生锁竞争的最大线程数。在JDK1.7中,实际上就是ConcurrentHashMap中的分段锁个数,即Segment[]的数组长度,默认是16,这个值可以在构造函数中设置。

如果自己设置了并发度,ConcurrentHashMap 会使用大于等于该值的最小的2的幂指数作为实际并发度,也就是比如你设置的值是17,那么实际并发度是32。

如果并发度设置的过小,会带来严重的锁竞争问题;如果并发度设置的过大,原本位于同一个Segment内的访问会扩散到不同的Segment中,CPU cache命中率会下降,从而引起程序性能下降。

ConcurrentHashMap1.8底层实现原理?

实际上就是HashMap的数据结构+synchronized锁,只需要锁住这个链表的头节点就不会影响其他的哈希桶数组元素的读写,大大提高了并发度。

为什么ConcurrentHashMap1.8需要去除Segments分段锁?

  • 1.7中锁的粒度太大了,对每个Segment加了锁,为了减少并发冲突的概率,去除了Segments分段锁。
  • JDK1.6以后 对 synchronized锁做了很多优化
  • 在大量的数据操作下,对于JVM的内存压力,基于API的ReentrantLock会开销更多的内存

ConcurrentHashMap1.8 put操作原理?

根据 key 计算出 hash 值,判断是否需要进行初始化;

定位到 Node,拿到首节点 f,判断首节点 f:

如果为 null ,则通过 CAS 的方式尝试添加;

如果为 f.hash = MOVED = -1 ,说明其他线程在扩容,参与一起扩容;

如果都不满足 ,synchronized 锁住 f 节点,判断是链表还是红黑树,遍历插入;

为什么ConcurrentHashMap1.8使用synchronsized而不用lock?

1.7lock锁是加上了while循环实现自旋,效率不高,JDK1.6以后 对 synchronized锁做了优化,

ConcurrentHashMap1.8如何让基于Node节点高效实现锁机制?

hash没有发生冲突的时候使用CAS,已经发生冲突的时候使用synchronized锁。

多线程同时执行CAS效率真的非常高吗?

不一定,极低的概率下会发生CPU飙升的情况。

ConcurrentHashMap1.8和ConcurrentHashMap1.7的区别

JDK1.7中ConcurrentHashMap全源码解读(CAS锁、分段锁) Segment分段锁机制,CAS锁机制,ConcurrentHashMapJDK1.7源码分析,ConcurrentHashMap的put方法、remove方法、get方法源码分析,ConcurrentHashMap锁的机制。RentrantLock,自旋锁,ConcurrentHashMap扩容机制,在1.7下存在的问题 阅读详情

相关推荐

【多线程】ConcurrentHashMap1.8源码保姆级解析

基于一个while循环,先去判断数组初始化了没有,如果没有,会再次比较sizeCtl的情况,如果正在初始化,则会通过Thread.yield()让出CPU资源,如果没有初始化,则会通过CAS的方式修改sizeCtl的值为-1,修改过后还会再次判断数组是否被初始化了(DCL,以免在当前线程修改sizeCtl的值的时候,已经有别的线程对当前数组完成了初始化),如果当前数组仍然没有被初始化,才会真正开始初始化。>0:代表当前数组已经初始化完成,此时sizeCtl的值代表当前数组的扩容阈值,或者数组的初始化大小。

aqin1012的博客 1929

4.3.3.1 并发容器类Map

MapJDK源码学习方法推理HashMap的实现数据结构数组链表二叉树红黑树红黑树定律:HashMap总结resize 扩容HashTableConcurrentHashMap带参构造器分析put的过程初始化方法 initTable转红黑树方法 :treeifyBin扩容方法: tryPresize (注意,核心重点)数据迁移:transfer (难点) JDK源码学习方法 逻辑思维能力是梳理学习方法的基础 养成线性思维:两个或者多个概念,攒成一条线 推理HashMap的实现 hashmap是key-va

LarZhu 528

ConcurrentHashMap 源码分析

ConcurrentHashMap在Java8中的实现改动较大,网上关于ConcurrentHashMap的文章也很少有基于java8的,将个人的一些理解记录下来以供分享。 Node ConcurrentHashMap底层是通过数组+链表(树)来实现的,数组中存储的就是Node。它与HashMap中的定义很相似,但是有一些差别它对value和next属性设置了volatile同步锁,它不允许.........

技术分享 605

深入解析HashMap和currentHashMap源码以及实现原理

深入解析HashMap和ConcurrentHashMapy源码以及底层原理 前言 HashMap 和ConcurrentHashMap,这两个相信大家都不陌生,在面试中基本上是必问的,以及在实际开发过程中也是比用的,那么看了这篇文章,无论在面试还是在实际开发中都可以顺手拈来,得心应手了。 HashMap 基于Map接口实现,元素以键值对的方式存储,并且允许使用null 建和null 值, 因为key不允许重复,因此只能有一个键为null,另外HashMap不能保证放入元素的顺序,它是无序的,和放入的顺序并

Venus's Blog 11万+

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

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

STM32F103C8T6心电采集实战:从PCB设计到上位机开发的完整指南(附TI杯开源资料)

本文详细介绍了基于STM32F103C8T6的心电采集系统开发全流程,从PCB设计到上位机开发,特别适合大学生电子设计大赛参赛者参考。内容涵盖硬件设计精要、软件算法优化及上位机开发技巧,并附TI杯开源资料,帮助开发者低成本实现医疗级心电信号采集。

kafka6courier的博客 194

ConcurrentHashMap源码分析

ConcurrentHashMap源码分析1.什么是ConcurrentHashMap?1.2ConcurrentHashMap是如何实现线程安全的?2.ConcurrentHashMap#put源码分析2.1 spread方法分析2.2 initTable()方法分析2.3 tabAt()方法2.4 casTabAt()2.5 addCount()2.5.1 addCount方法思想2.5.2 ...

weixin_40969748的博客 599

cmu15445(2024fall) project 2

project 2比p1难度要更大,代码量更大且涉及的知识更多,实现中出现的bug更多,好在是最后全部解决了。我最大的收获在于对于多线程编程的理解更深入了,实际发现解决了多个多线程相关的bug,学会了更多多线程下debug的技巧方法,也见识了更多多线程的bug情况。在之后可以更系统的学习一下多线程编程。同时学会实现B+树也是需要掌握的基础。

Jerome_Yi的博客 1563

java ConcurrentHashMap源码分析

java ConcurrentHashMap源码分析

水w的博客 1511

【大模型】MHA,MQA,GQA及KV Cache详解

在自回归模型中(Autoregressive Models),模型会逐个生成文本的每个token,这个过程可能比较慢,因为模型一次只能生成一个token,而且每次新的预测都依赖于之前的上下文。这意味着,要预测第1000个token,你需要用到前999个token的信息,这通常涉及到对这些token的表示进行一系列矩阵乘法运算。而要预测第1001个token,你不仅需要前999个token的信息,还要加上第1000个token的信息。

酒酿小圆子呀~ 2616

PDS联合Modelsim仿真报错(Error loading design) line6报错 -novopt已禁用 (全网最详细!!!!!)

发现这个文档是Pango自动生成的,也就是说每一次我们点击"Run Behavior Simulation"的时候,PDS都会自动生成一次这个./run_behav_simulate.tcl文件,所以就算我们这一次把这个文件修改了,我点击"Run Behavior Simulation"想让它再跑一次正确的仿真,它此时又重新生成了这个带有-novopt的错误tcl文件,仿真依旧不能成功。问题真的是出在这里,那我们修改这个./run_behav_simulate.tcl文件就好了,但事实并非如此!

weixin_74058783的博客 3193

HashMap和ConcurrentHashMap 源码分析

注:本文所述为JDK1.8版本 HashMap和ConcurrentHashMap源码分析 1、HashMap 1.1 概述 HashMap底层是基于数组+链表+红黑树组成的。 数据结构示例图: 主要属性: static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 static final int MAXIMUM_CAPACITY = 1 << 30; static .

loki的博客 283

LIN 协议测试

如下图,使用PicoScope示波器抓取报文Id为0x18的波形,数据场Byte1的长度为519.994μs,Byte1共10bit,计算位时间:tBit= 519.994/10 = 51.9994μs,符合预期。■ 唤醒信号是一个250μs到5ms的显性电平,发送唤醒信号的节点发送唤醒信号后150ms~250ms没有收到其他节点的唤醒信号,发送唤醒信号的节点发送新的唤醒信号。对于LIN主节点,检查发送的帧头长度是否符合设计要求,对于LIN从节点,检查从节点能否正确响应设计范围之内的帧头长度。

m0_63902994的博客 5937

[集合源码]——ConcurrentHashMap源码分析

通过图解,以及深入源码学习,用简单易懂的话让你学习ConcurrentHashMap原理,让你不再畏惧源码

热爱编程的机械生的救赎 2924

深入浅出SSD:固态存储核心技术、原理与实战(文末赠书)

作为经典畅销书的升级版,本书在第1版的基础上新增了大量新知识(如闪存、UFS、测试、文件系统等),并改写或删减了许多过时或不适用的内容,总修改量超过60%。本书由SSD技术社区SSDFans的核心团队成员联合业界、高校SSD方面的专家共同完成,相对于第1版,内容更全面、更新,也更有深度。本书从产品、技术和应用3个维度展开,全面、详细讲解了SSD的发展历史、产品形态、工作原理、整体架构,以及各个功能模块的原理和使用,从SSD的前端接口协议到FTL算法,再到后端的闪存和纠错,以及测试,本书都有涉及。

阿玥的小博客 3126

解决[WARN] : Controller Spawner couldn‘t find the expected controller_manager ROS interface.思路

rrbot例程ros_control配置问题的解决思路 本人刚入门ROS机器人开发,在学习rrbot例程出现了control_manager无法启动的bug。 在启动rrbot_control.launch的时候,出现下列warning: [WARN] [1594362016.372059]: Controller Spawner couldn't find the expected controller_manager ROS interface. 之后再按教程的步骤 rosservice call /

qq_40299650的博客 1万+

ConcurrentHashMap原理与源码分析

在Java并发编程中,是一个至关重要的数据结构,它解决了普通HashMap在多线程环境下的线程安全问题,同时克服了Hashtable和在高并发场景下的性能瓶颈。本文将深入分析的设计原理、内部实现和源码细节,帮助读者彻底理解这一高性能并发集合类。

weixin_54879811的博客 2368

乐企归集能力说明文档V2.0006

纳税人销售货物、提供服务开具的增值税发票及购进货物、接受服务取得的增值税发票、海关缴款书(含出口转内销证明)、代扣代缴完税凭证,可以通过本能力进行归集。纳税人可将本能力中的各类规则和接口嵌入本单位信息化系统中(如:销售、报销、财务等),实现发票自动化归集,便于纳税人完成发票入账、抵扣、退税以及代办退税等操作,有效提升涉税业务办理效率。

记录点滴 574

ConcurrentHashMap ?这份源码分析了解一下

在JDK 8中的ConcurrentHashMap一共有5个构造方法,这几个构造方法中都没有对内部的数组做初始化, 只是对一些变量的初始值做了处理,其中ConcurrentHashMap的数组初始化是在第一次添加元素时完成的。当我们调用上面这个方法得到的初始容量,和HashMap以及JDK 7中的ConcurrentHashMap不同,即使我们传递的是一个2的幂次方数,该方法计算出来的初始容量依然是比该值大的2的幂次方数。注意:这些构造方法中,都会涉及sizeCtl变量,它是在构造方法里面的作用非常重要。

m0_67597657的博客 535

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

上一篇: RabbitMQ多种问题出现的解决方案
下一篇: 你了解分布式事务吗?
Java_ttcd
博客等级 码龄4年 198粉丝 190原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值