ConcurrentHashMap1.7源码 随笔

ConcurrentHashMap




实例化方法

在HashMap中, 主要有2个参数 一个 是 初始容量大小 , 一个是 负载因子

但是在ConcurrentHashMap中 , 还出现了另外一个 重要的 参数, 叫做 并发等级

在所有的构造方法中, 主要有2个构造方法, 一个是 具有3参数的 正常构造器, 一个是基于 Map的构造器 ,

基于map的构造器, 我们可以先不用看 . .

在正常的构造器中, 构造主要有 3个步骤

  1. 判断参数是否正常
  2. 参数处理
  3. 实例化Segment数组

第一个步骤 : 判断参数是否正常

if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
    throw new IllegalArgumentException();

都不允许是负数. 使用 !(localFacory>0) 是因为 负载因子 同时不能为0


第二个步骤 : 处理参数

这里可以细分为 处理并发等级 和 处理初始容量 2个小步骤

// 2.1 处理并发等级

if (concurrencyLevel > MAX_SEGMENTS)		//MAX_SEGMENTS = 2^16 最大的Segment数量.
    concurrencyLevel = MAX_SEGMENTS;		//可以把并发等级看成是Segment数量 
/*
    这边 可以做一些简单的猜测... 之前对HashMap的观察 
    一个key hash 出来之后 肯定是一个int的数 , 这个数 有32位
    sshift 在这个并发等级下, 需要多少位,, 也就是说 用多少位来表示 segment数量
    ssize 目前来看就是 并发数量... 也就是Segment 实际的数量
    ssize >= concurrencyLevel 且是一个 2次方数
*/
int sshift = 0;	
int ssize = 1;
while (ssize < concurrencyLevel) {
    ++sshift;
    ssize <<= 1;
}
/**
	有了上面的猜测.. 看一下这里. 这里保留了2个参数
	一个是 segmentShift : 32 - sshift , 也就说 除了用来区分Segment..剩下的位数..
	这个位数 可能是 用来做 hash 分到Segment 之后再分配到 table里
*/
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;	// ssize 是一个 2次方数..所以 - 1 会造成低位全部都是 1
//根据翻译..Segment 掩码.. 掩码就是 掩盖掉 不需要的位数...

// 2.2 处理初始容量

if (initialCapacity > MAXIMUM_CAPACITY)		//MAXIMUM_CAPACITY = 2^30 这个和HashMap是一样的
    initialCapacity = MAXIMUM_CAPACITY;
int c = initialCapacity / ssize;		//ssize 当时是说 比并发等级大 且是 一个2次方数
//但是注意..这里的初始容量并没用和hashmap一样做处理..没有形成一个2的次方数
//但是因为底层是一个Segment数组..所以最后是使用ssize为基准的. 最后还是一个
//ssize当时说可以看成是一个 Segment的数量... 那么 整体容量 / Sement数量... 这不就得到了每个Sement大概需要多少个 table 吗
if (c * ssize < initialCapacity)	// 因为 / 可能出现除不尽的现象.. 这就导致 c* ssize 可能小于初始值.. 这里要再加回来.. 最后结果 实际 c * ssize >= 初始容量
    ++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;	//MIN_SEGMENT_TABLE_CAPACITY=2 Segment中的table数组,最小为 2
/**
这里就出现了一个奇怪的现象.... 默认的容量是16 , 默认的并发等级是16 但是每个Segment最小的table数组长度又是2 , 
这就导致了 虽然看上去 初始化容量是16 .. 但是底层实际是有 32 个长度的 hashEntry数组
*/
while (cap < c)	//cap 就是 每个Segment中的 table数 cap最后不得小于c 且是一个2次方数
    cap <<= 1;

第三个步骤 : 对Segment数组进行实例化

/**
	这里发现了个东西... 它首先去实例化一个叫s0的东西..用来保存了 负载因子..threshold阈值 , 以及一个需要的HashEntry的数组....ss 是最后需要的 Segment数组.. 最后使用了一个 UNSAFE的方法(UNSAFE的方法的都是native方法- -不知道干嘛)...这因为命名叫s0 .. 又是数组.. 这里猜测它就是把 s0放进 ss数组的下标为0的位置
	ps : 那为啥不先 ss = new Segment[ssize]; ss[0] = new Segmeng.....一定要炫技的吗?还是说为了提升速度
		还是说使用UNSAFE快一些
		这里纯属猜测..继续深入下去 才能知道 什么情况..
	原来的hashMap是允许 key 作为null 的 而且存放在 0的位置...
	但是Concurrent是不能为null的, value 也不能为null
**/
Segment<K,V> s0 =
    new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                     (HashEntry<K,V>[])new HashEntry[cap]);
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];		//ssize是实际的Segment数量..这里也能看出来
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;



Map为Put而生

在ConcurrentHashMap中 使用的底层存储 单元 是一个名叫 Segment的东西

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

继承了锁的重入锁的能力…

在每个Segment中存在实际的 存储单元 HashEntry 数组

transient volatile HashEntry<K,V>[] table

对于一个Map 最重要的 方法 之一 就是 put

public V put(K key, V value) {
    Segment<K,V> s;		//寻找出要存储的Segment 区间... 还是靠key的hash 来分区..
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);		
    int j = (hash >>> segmentShift) & segmentMask;	
    if ((s = (Segment<K,V>)UNSAFE.getObject          
         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
        s = ensureSegment(j);	//因为之前虽然new了一个数组..但是里面的元素都是空的.. 也就是会出现懒加载的现象.. 之后再实例化. 这个ensureSegment方法大概就是了. 
    //不过底层使用的是UNSAFE 来直接获得Segment....
    return s.put(key, hash, value, false);	//调用 Segment的Put方法
}
/****
if (!map.containsKey(key))
   return map.put(key, value);
else
   return map.get(key);
**/
public V putIfAbsent(K key, V value) {	//和上面唯一的区别 就是 最后一个参数为true
    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
         (segments, (j << SSHIFT) + SBASE)) == null)
        s = ensureSegment(j);
    return s.put(key, hash, value, true);
}

这样之后…就从map的 put方法 转移到了 Segment的 put方法

在这之前先不要着急… 让我们来分析一下 hash() 方法 以及 这个 j 是如何运算的

private int hash(Object k) {
    int h = hashSeed;		//这个hashSeed 是和 虚拟机的参数有关.. 在不配置的情况下 默认为0 .我们就是用默认的分析吧

    if ((0 != h) && (k instanceof String)) {	//如果配置了参数..且是String类型的, 就使用hash32
        return sun.misc.Hashing.stringHash32((String) k);
    }

    // k.hashCode ... 这里可以看出..虽然 key 没有明确判断不能为null ..但是 一旦是null
    //这里就会空指针了...
    h ^= k.hashCode();		//hashmap一样的操作..

    // Spread bits to regularize both segment and index locations,
    // using variant of single-word Wang/Jenkins hash.
    //这里发生变化了! 看上去一顿乱移...emm 应该是某个hash算法... 反正就是为了增加散列度...
    h += (h <<  15) ^ 0xffffcd7d;
    h ^= (h >>> 10);
    h += (h <<   3);
    h ^= (h >>>  6);
    h += (h <<   2) + (h << 14);
    return h ^ (h >>> 16);
}

关键的来了… 让我们来分析一下这个 j 是个啥运算

/*
	在上文中.. 我们知道了 segmentShift 是 除掉了Segment位数之后的位置 , segmentMask 是个低位全部都是1的数
	这样的运算 首先让 hash 右移 (32-segment数量)...
	这里也就是说 高segment位 是用来区分segment的 & 一个掩码
	做几个例子 更加直观  . 加入就以默认的来算  segmentShift = 28 (2^4 = 16)(32 - 4 = 28)
	segmentMask = 0000...0000 1111
	最后得到的结果 也就是取 高位.
	
	ps: 这里为什么要使用无符号的移位运算呢 .. ssize = 2^sshift
    segmentShift = 32 - sshift
    segmentMask = ssize - 1
    也就是说 如果 hash >>> segmentShift 不就是最后的j了吗- -
    为啥还要再& 一下 掩码...emm
**/
int j = (hash >>> segmentShift) & segmentMask;

也就是说 这个 j 可以表示的是 放入到哪个segment中去… 之后UNSAFE 就是使用j 来找到对应的 Segment…

有了j之后… 让我们看看 ensureSegment(j) 如何实例化这个 Segment 对象的

ConcureentHashMap , 都知道 是个并发容器… 也就是意味着是支持多个线程同时访问的…

所以先要判断 这个方法会不会被多个线程同时进入 而且 k相同? 答案是肯定的…

但是我们并没有发现任何的锁…那么如何保证线程安全呢 ? CAS ( 看这个方法是如何使用 CAS 进行线程安全的)

private Segment<K,V> ensureSegment(int k) {
    //这里是都是简单流程.. 通过k 获得 u(应该是某个偏移量) 通过u再获得对应的segment
    final Segment<K,V>[] ss = this.segments;
    long u = (k << SSHIFT) + SBASE; // raw offset
    Segment<K,V> seg;
    //第一次获取一下 比较.. 可能前面有个线程跑的比较快 提前创建好了. 那没事 直接返回
    if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
        Segment<K,V> proto = ss[0];  // s0出现了! 也就是说 s0可能是作为样板存在的
        int cap = proto.table.length;
        float lf = proto.loadFactor;
        int threshold = (int)(cap * lf);
        //数据都都拷贝下来  
        HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
            == null) { // recheck 再次检查
            Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);	//new 还是new出来了
            //也就是说 多线程走到这里的话.. 空间还是损耗掉了
            //使用一个自旋的CAS 来保证线程安全
            while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                   == null) {
                //如果不为true 就是说明有线程已经创造好了 , 当前线程再次进入while判断的时候就会false正常结束
                //如果为true 说明当前线程是第一个完成的, 正常break 跳出
                //造成的线程安全
                if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                    break;
            }
        }
    }
    return seg;
}

上面这些都捋完了… 终于来到了 Segment的 put方法了

疯狂粘贴源码…篇幅好像过长… 所以这里尝试截断的方式来…

首先put方法也会同时被很多很多的线程同时访问… 之前说了Segment是继承了锁的.意味着可以随时随地的使用lock来保证线程安全 所以 put方法 可以大致 描述成

  1. 尝试一下能不能拿到锁, 拿到锁 就进入正常的 put方法, 如果拿不到锁, 就进入一个准备阶段scanAndLockForPut方法
HashEntry<K,V> node = tryLock() ? null :	//开篇先使用一下tryLock 看看能不能得到锁
    scanAndLockForPut(key, hash, value);
  1. 如果得不到锁 就会进入 scanAndLockForPut 准备 出一个新的 node … ps: 虽然感觉没必要… 听我慢慢解释

    private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
        HashEntry<K,V> first = entryForHash(this, hash);
        HashEntry<K,V> e = first;
        HashEntry<K,V> node = null;
        int retries = -1; // negative while locating node
        while (!tryLock()) {
            HashEntry<K,V> f; // to recheck first below
            //假设在一直失败的情况下.. retries = -1
            if (retries < 0) {
                if (e == null) {
                    if (node == null) // speculatively create node
                        node = new HashEntry<K,V>(hash, key, value, null);
                    retries = 0;        //如果 没有找到节点.. 意味着是新的.. 先构造出一个节点. retries设置为0
                }
                else if (key.equals(e.key)) //如果 找到了一个节点.. 这个节点是存在的
                    retries = 0; //也设置为0 重试
                else
                    e = e.next;     //否则接着寻找
            }
            else if (++retries > MAX_SCAN_RETRIES) {
                // 思考~ 如果有线程lock住了... 会不会死锁.
                lock(); //如果超过了重试次数.. 那就直接 使用lock阻塞. 直到强到锁位置
                break;
            }
            else if ((retries & 1) == 0 &&  //retries & 1 也就是说 每2次 就去看一眼自己的 槽有没有发生改变..
                     (f = entryForHash(this, hash)) != first) {
                //如果发生改变... 那么就重新寻找
                e = first = f; // re-traverse if entry changed
                retries = -1;
            }
        }
        /**
         * 这里会不会出现  上面分支 最后一条 判断的时候 还没有table 还没有改变
         * 判断结束之后 刚发生变化
         * 然后 tryLock的时候 获取了锁...这样导致返回的 node 虽然不是null 但是 实际 key 是存在的- -?
         * */
        //node有值 说明是新的点
        //node 没有值 说明是有的..只需要修改
        return node;
    }
    

从源码中 我们可以看出来… retries & 1 也就是以为这 每2次 trylock失败 就会查询一遍 当前插入的槽 是否已经出现了改变, 如果出现改变, 那就重新准备 , 但是并非是一直在轮询… 也就是说node 其实并不相信 , node 为 null 可能是有重复值… node有值 也可能在trylock成功之前被其他线程修改了… 那么如何处理这样的情况呢

  1. 进入正常的put ( 隐藏了方法体 ) (这一段代码 是lock 之后的, 也就是说 临界区)

    V oldValue;
    //获得了锁
    try {
        HashEntry<K,V>[] tab = table;
        int index = (tab.length - 1) & hash;		//使用和hashmap相同的方式获得index
        //回顾一下.. hashtable 的index计算方式是 先去掉 hash值的符号位
        //然后 在 % tab的长度
        HashEntry<K,V> first = entryAt(tab, index);
        for (HashEntry<K,V> e = first;;) {	//依然需要遍历一遍.. 来确定 node 是否有用...
            //ps: 所以说感觉- -那个scanAndLockForPut去准备一个node没啥必要- -...就正常循环着等待也行(可能是能快一点是一点吧)
            if (e != null) {	//下面是正常的寻找节点的方式
                K k;
                if ((k = e.key) == key ||
                    (e.hash == hash && key.equals(k))) {
                    //如果之前有的就替换
                    oldValue = e.value;
                    if (!onlyIfAbsent) {//区分put 和 putIfAbsent (put 是会替代的 , 但是putIfAbsent只插入不存在的值...存在的话 就返回  执行结束)
                        e.value = value;
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            }
            else {		//不存在这个点 
                if (node != null)		//如果是 scan 那个方法进来的话,,,就有可能提前准备好了
                    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);	//使用UNSAFE方法 塞进去...
                ++modCount;		//结构修改次数+1
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        unlock();	//解锁..
    }
    return oldValue;
    
扩容机制

在HashMap中,由于它的底层是用Entry数组存储的, 意味着 resize 是HashMap的方法. 而且不用考虑多线程的问题

而在ConcurrentHashMap中, 虽然扩容的时候是需要lock的, 也就是只有一个线程在扩容, 但是不排除 可能有其他的线程在遍历 , 因此要比HashMap更加的复杂, 整体相似, 也是扩容到原来的2倍容量

private void rehash(HashEntry<K,V> node) {
    HashEntry<K,V>[] oldTable = table;
    int oldCapacity = oldTable.length;
    int newCapacity = oldCapacity << 1;		//扩容到原来的2倍
    threshold = (int)(newCapacity * loadFactor);	//重新设置阈值
    HashEntry<K,V>[] newTable =
        (HashEntry<K,V>[]) new HashEntry[newCapacity];
    int sizeMask = newCapacity - 1;
    for (int i = 0; i < oldCapacity ; i++) {
        HashEntry<K,V> e = oldTable[i];
        if (e != null) {	//如果说这个槽里面没有值..那自然不需要移动处理	
            HashEntry<K,V> next = e.next;
            int idx = e.hash & sizeMask;
            if (next == null)  //如果单个节点..就直接移动
                newTable[idx] = e;
            else { //这里就复杂了.... 不像1.7 HashMap 直接使用 头插法 一个一个移过去了
                //首先我们知道.. 扩容2倍之后, 就意味着索引要么改变成index+2^n , 要么不变
                //根据写源码的人的统计 使用这种方式进行.. 需要克隆的点只有1/6左右 (很强..不知道是怎么做到的)
                HashEntry<K,V> lastRun = e;		//头节点以及它的idx
                int lastIdx = idx;
                //循环出一个不变的最后长链
                for (HashEntry<K,V> last = next;
                     last != null;
                     last = last.next) {
                    int k = last.hash & sizeMask;
                    if (k != lastIdx) {
                        lastIdx = k;
                        lastRun = last;
                    }
                }
                //把这个长链子 直接移动过去
                newTable[lastIdx] = lastRun;
                
                /***
                	太强了这个东西. 这里为什么需要 new ? 为什么 不能直接移动过去?
                	解 : 因为是个并发容器. 在扩容的期间 不能保证是否 有其他的线程正在读,
                	不能保证造成的结果 就是 扩容前的表格 是一定要保留下来的, 但是如果直接保留下来的话, 就额外需要非常多的空间, 所以正确的做法是 ,不能够破坏以前的链子结构
                	不破坏,又要扩容, 那只能 对以前的节点进行克隆移动了
                **/
                for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
                    V v = p.value;
                    int h = p.hash;
                    int k = h & sizeMask;
                    HashEntry<K,V> n = newTable[k];
                    newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
                }
            }
        }
    }
    //reset结束之后 直接把这个节点插入...
    int nodeIndex = node.hash & sizeMask; // add the new node
    node.setNext(newTable[nodeIndex]);
    newTable[nodeIndex] = node;
    table = newTable;
}

图解为何能做到边扩容 边保证 其他线程对扩容前的HashEntry可见 (容量不正确不要在意~举个栗子而已)

在这里插入图片描述


如果不需要扩容的话 就直接调用setEntry方法.

static final <K,V> void setEntryAt(HashEntry<K,V>[] tab, int i,
                                   HashEntry<K,V> e) {
    UNSAFE.putOrderedObject(tab, ((long)i << TSHIFT) + TBASE, e);
}	//这个方法没啥好说的- -..直接使用UNSAFE方法塞进去


只有Put没有Get有何用?

终于弄清楚了put方法… 对于一个map来说…只有put存储…取不出有啥用呢

public V get(Object key) {
    //没有任何的锁...非常的自在 , 高并发 就是强
    Segment<K,V> s; // manually integrate access methods to reduce overhead
    HashEntry<K,V>[] tab;
    int h = hash(key);
    //通过(h >>> segmentShift) & segmentMask)获得插入那个插槽, 然后计算出偏移量 u
    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
    if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
        (tab = s.table) != null) {
        //除了都是使用UNSAFE 获取到对象...其他都是正常的寻找....
        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;
}


Remove还是需要的

get方法还是简单的…没有什么复杂度. 但是就是因为没有什么复杂度, 又没有加锁… 所以才非常的高效

对于一些不需要的节点…我们还是会去执行remove方法来降低整个map的大小

那么remove到底会怎么样呢? 写写是否会发生互斥 是如何解决多线程的问题…开始

public V remove(Object key) {		//remove方法的入口
    int hash = hash(key);	//直接计算出key的hash 值
    Segment<K,V> s = segmentForHash(hash);	//通过hash来获取应该在哪个Segment中
    return s == null ? null : s.remove(key, hash, null);
}
public boolean remove(Object key, Object value) {
    int hash = hash(key);
    Segment<K,V> s;
    return value != null && (s = segmentForHash(hash)) != null &&
        s.remove(key, hash, value) != null;
}
private Segment<K,V> segmentForHash(int h) {
    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
    return (Segment<K,V>) UNSAFE.getObjectVolatile(segments, u);
}

好! 又转移到了 让Segment 去remove 这个值

这种会引起 Entry 变化的 又要安全的… 通常是需要使用锁的… remove方法也不例外… 开篇现需要trylock一下

if (!tryLock())
    scanAndLock(key, hash);

我们先假定没有抢到锁~ 进入 scanAndLock方法

private void scanAndLock(Object key, int hash) {
    // similar to but simpler than scanAndLockForPut
    //翻译过来就是说 和 scanAndLockForPut 相似~ 所以不慌了
    HashEntry<K,V> first = entryForHash(this, hash);
    HashEntry<K,V> e = first;
    int retries = -1;	//依然是记录尝试的了多少次
    while (!tryLock()) {	//如果没有抢到锁的话... 就提前去看看有没有需要删除的值
        HashEntry<K,V> f;
        if (retries < 0) {
            if (e == null || key.equals(e.key))
                retries = 0;	//最终都会进入到这个地方...要么找到要么找不到..
            // emmmm 感觉都是无用功- -反正 扫描一遍完事之后 就开始累加自己的扫描次数
            //每2次扫描就会去检查一下当前槽是不是发生改变了, 改变了的话接着扫描...
            else
                e = e.next;
        }
        else if (++retries > MAX_SCAN_RETRIES) {
            lock();
            break;
        }
        else if ((retries & 1) == 0 &&
                 (f = entryForHash(this, hash)) != first) {
            e = first = f;
            retries = -1;
        }
    }
}
// emmmm ps : 讲道理… 我是真的没搞懂 scan xxx 的方法 到底有啥实际的用途…仅仅只是慢一点抢锁吗? 有大佬知道的话能告诉我一下… 只是减少线程竞争吗
继续

当抢到了锁之后, 进入正常的remove 逻辑

V oldValue = null;
try {
    HashEntry<K,V>[] tab = table;
    int index = (tab.length - 1) & hash;
    HashEntry<K,V> e = entryAt(tab, index);
    HashEntry<K,V> pred = null;
    while (e != null) {		//正常遍历
        K k;
        HashEntry<K,V> next = e.next;
        if ((k = e.key) == key ||		//要么直接就是同一个key . 要么就是节点的hash值对上,key的equals方法对上 (注意 : 节点的hash 其实就是key 的hash进行一顿乱移 , 所以还是和 key的hashcode有关)
            (e.hash == hash && key.equals(k))) {
            V v = e.value;		//找到了这个值..
            //value为null 意味着直接删除
            //反正就是查看一下传入的value 和 这个 v 是否相同
            if (value == null || value == v || value.equals(v)) {
                if (pred == null)	//删除的是头节点 , 那就重新设置头节点
                    setEntryAt(tab, index, next);
                else
                    pred.setNext(next);	//否则的话就让前一个节点直接关联后面的节点, 
                ++modCount;
                --count;
                oldValue = v;
            }
            break;	//已经结束了
        }
        pred = e;
        e = next;
    }
} finally {
    unlock();
}
return oldValue;

在这个方法中 发现了一个和HashMap区别的地方

在HashMap中 的Entry 的hashcode 方法 是 key的hashcode ^ value的hashcode , 但是在ConcurrentHashMap中, 是使用 把key的hash 一顿移动得到的hashcode

尾声

到这里就已经接近尾声了… map 主要还是 3个方法 put get remove …

剩下的方法 也就是 一些获得迭代器…使用迭代器 调用 map本身的方法

还剩下一个 ConcurrentMap 接口中 得到的 一些replace 方法

public boolean replace(K key, V oldValue, V newValue) {
    int hash = hash(key);
    if (oldValue == null || newValue == null)
        throw new NullPointerException();
    Segment<K,V> s = segmentForHash(hash);
    return s != null && s.replace(key, hash, oldValue, newValue);
}
public V replace(K key, V value) {
    int hash = hash(key);
    if (value == null)
        throw new NullPointerException();
    Segment<K,V> s = segmentForHash(hash);
    return s == null ? null : s.replace(key, hash, value);
}

前面部分还是熟悉的部分… 直接交给 Segment 去运行 replace方式

final boolean replace(K key, int hash, V oldValue, V newValue) {
    if (!tryLock())
        scanAndLock(key, hash);	//熟悉的配方...
    boolean replaced = false;
    //寻找 设值...
    try {
        HashEntry<K,V> e;
        for (e = entryForHash(this, hash); e != null; e = e.next) {
            K k;
            if ((k = e.key) == key ||
                (e.hash == hash && key.equals(k))) {
                if (oldValue.equals(e.value)) {
                    e.value = newValue;
                    ++modCount;		//modCount 会发生改变! 
                    replaced = true;
                }
                break;
            }
        }
    } finally {
        unlock();
    }
    return replaced;
}
//这里也是设值 . 不过 不给定原来的值.. 
final V replace(K key, int hash, V value) {
    if (!tryLock())
        scanAndLock(key, hash);	//相同的配方.
    V oldValue = null;
    try {
        HashEntry<K,V> e;
        //寻找是否有... 修改 保留久值. 返回  结束~
        for (e = entryForHash(this, hash); e != null; e = e.next) {
            K k;
            if ((k = e.key) == key ||
                (e.hash == hash && key.equals(k))) {
                oldValue = e.value;
                e.value = value;
                ++modCount;
                break;
            }
        }
    } finally {
        unlock();
    }
    return oldValue;
}
不是总结的总结

这个东西真的很强…

没有总结 - -. 背板也没意思~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值