ConcurrentHashMap源码分析

ConcurrentHashMap

多线程的情况下如何保证HashMap线程安全

  • Collections.synchronizedMap(Map)创建线程安全的map
  • 使用HashTable
  • 使用ConcurrentHashMap

JDK1.7中的ConcurrentHashMap

  • JDK1.7及之前版本的ConcurrentHashMap,在内部维护了一个分段锁类(Segment)
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;
    // 快速失败(fail—fast)使用
    transient int modCount;
    // 大小
    transient int threshold;
    // 负载因子
    final float loadFactor;
}

​ 在Segment中,使用了volatile修饰了参数table,且Segment继承与ReentrantLock

JDK1.7中如何做到线程安全

  • 使用了分段锁技术

  • 每次put的时候,先访问Segment,然后在进行操作

    public V put(K key, V value) {
        Segment<K,V> s;
        //如果为空,抛出空指针异常
        if (value == null) throw new NullPointerException();
        //......省略
        return s.put(key, hash, value, false);
    }
    
  • 并发度由数组大小决定,例如当前大小16,那么并发度就是16

ConcurrentHashMap的Get方法是如何操作的

  • 在HashMap的基础上,先定位到Segment,在定位到元素
  • 在ConcurrentHashMap的get方法中,全程不需要加锁

JDK1.8中的ConcurrentHashMap

ConcurrentHashMap在JDK1.8中做了什么修改

  • JDK1.8中抛弃了Segment分段锁,采用了CAS + synchronized来保证并发安全

CAS是什么

  • CAS是一种乐观锁的实现方式
  • 在线程读取数据时不加锁,写回数据的时候,会进行原值比较,查看原值是否被其他线程修改了
    • 如果没修改,则写回
    • 如果修改了,则重新读取,并执行流程

CAS是否一定安全

  • 不一定,例如ABA问题,CAS就无法判断了
    • ABA问题:初始值为A,线程B将A改为B,线程C将B改为A,此时线程A就无法判断是否修改过了

当CAS无法保证安全,那么应该如何解决

  • 可以加入版本号、时间戳等信息用于辅助

为什么JDK1.8中使用了重量级锁synchronized

  • 因为在JDK1.6中,JVM对synchronized进行了优化,由原来的重量级锁换成锁升级的方式。
  • 锁升级最初是使用轻量级锁进行锁定的

什么是锁升级,锁升级的顺序是什么

  • 先试用偏向锁,优先同一线程并再次获取锁
  • 如果失败,使用CAS轻量级锁
  • 如果失败,开始短暂自旋,防止线程被挂起
  • 如果以上都失败,则升级为重量级锁

ConcurrentHashMap成员变量

sizeCtl -> 初始化和扩容时使用

/**
 * sizeCtl取值范围:
 * 	 0:默认
 *   -1:哈希表正在初始化
 *   >0:相当于HashMap中的threshold,扩容阈值
 *   <-1:代表有多个线程正在扩容(例如:-2就代表有(2-1=1)个线程正在扩容)
 */
private transient volatile int sizeCtl;

ConcurrentHashMap数据结构

Node类

static class Node<K,V> implements Map.Entry<K,V> {
    //key计算出的hash值
    final int hash;
    //key值
    final K key;
    //value值
    volatile V val;
    //链表使用
    volatile Node<K,V> next;
    //.....省略
}
  • 与HashMap不同的是,ConcurrentHashMap中的value和next属性加上了volatile修饰
  • volatile:保证了可见性,即多线程运行时,强制读取此元素(共享变量),是一种多线程中的轻量级同步机制

ConcurrentHashMap的初始化

ConcurrentHashMap构造方法

/**
 * 默认空构造方法,创建大小为默认值(16)
 */
public ConcurrentHashMap() {}

/**
 * 传入数组大小的构造方法
 * @param: initialCapacity 数组大小
 */     
public ConcurrentHashMap(int initialCapacity) {
    //创建的大小为传入值(initialCapacity),负载因子为默认(0.75f)
    //1为预计参与运行的线程数(在jdk1.8没有作用了)
    this(initialCapacity, LOAD_FACTOR, 1);
}


public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
    this.sizeCtl = DEFAULT_CAPACITY;
    putAll(m);
}

/**
 * 传入数组大小及负载因子的构造方法
 * @param: initialCapacity 数组大小
 * @param: loadFactor 负载因子
 */ 
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
    this(initialCapacity, loadFactor, 1);
}

/**
 * ConcurrentHashMap初始化真正运行的方法
 * @param: initialCapacity 数组大小
 * @param: loadFactor 负载因子
 * @param: concurrencyLevel预计参与运算的线程数(JDK1.7中使用,用于设置分段锁Segment的数量,JDK1.8中无作用)
 */ 
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
    //负载因子小于0,或数组大小小于0,或参与运算的线程数小于0,抛出异常
    if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
        throw new IllegalArgumentException();
    //如果数组大小小于运行的线程数
    if (initialCapacity < concurrencyLevel)   
        //设置大小为传入的线程数
        initialCapacity = concurrencyLevel;   
    //使用负载因子对数组容量进行计算,实际上手动传入的loadFactor只在此处使用
    long size = (long)(1.0 + (long)initialCapacity / loadFactor);
    //将计算出的size进行转换,若大于最大值,则设置为最大值,否则使用tableSizeFor进行转换,返回的结果为最接近size的2的幂数
    int cap = (size >= (long)MAXIMUM_CAPACITY) ? 
        MAXIMUM_CAPACITY : tableSizeFor((int)size);
    //设置sizeCtl,表示设置数组大小
    //这个构造方法并没有真正的初始化ConcurrentHashMap,这里只是为了初始化而做准备
    this.sizeCtl = cap;
}

/**
 * 返回大于输入参数且最接近的2的幂数
 */
private static final int tableSizeFor(int c) {
    int n = -1 >>> Integer.numberOfLeadingZeros(c - 1);
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

ConcurrentHashMap真正的初始化方法

/**
 * ConcurrentHashMap真正初始化方法
 * 只有在对数据进操作时才调用(putVal,merge方法等)
 */
private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    //当table数组为空时,才进行循环初始化,否则直接返回
    while ((tab = table) == null || tab.length == 0) {
        //如果sizeCtl小于0,表示有其他线程正在对数组进行扩容
        if ((sc = sizeCtl) < 0)
            //释放当前线程资源,重新获取
            Thread.yield(); 
        //U.compareAndSetInt()为CAS的方法
        //使用CAS,将sizeCtl变为-1,表示当前线程正在对数组进行初始化
        else if (U.compareAndSetInt(this, SIZECTL, sc, -1)) {
            try {
                //数组为空才进行操作
                if ((tab = table) == null || tab.length == 0) {
                    //DEFAULT_CAPACITY为数组默认大小16
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    //初始化Node数组
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    //设置扩容阈值,等同于HashMap中的LOAD_FACTOR
                    //这里等于n * 0.75
                    sc = n - (n >>> 2);
                }
            } finally {
                //设置sizeCtl值
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

ConcurrentHashMap取值

计算index值

static final int spread(int h) {
    //HASH_BITS = 0x7fffffff
    //为了保证计算后的值为正数
    return (h ^ (h >>> 16)) & HASH_BITS;
}

为什么要与HASH_BITS进行计算?

  • 为了保证计算后的值为正数,在二进制中,第一位为0代表正数,为1代表负数

get()方法

/**
 * 通过key寻找value
 */
public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    //使用key的hash值获取key的index
    int h = spread(key.hashCode());
    //当数组不为空,且当前index位置有值的时候,才进行寻找
    if ((tab = table) != null && (n = tab.length) > 0 &&
        //这里的e调用了tabAt()方法
        (e = tabAt(tab, (n - 1) & h)) != null) {
        //hash相等
        if ((eh = e.hash) == h) {
			//此时为数组,获取首节点
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        //此时为红黑树
        else if (eh < 0)
            //使用Node中的find方法寻找,见下方
            return (p = e.find(h, key)) != null ? p.val : null;
        //此时为链表
        while ((e = e.next) != null) {
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

tabAt()方法

/**
 * 寻找数组在内存中index位置的数据
 * 使用Unsafe,直接操作内存
 */
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
    return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}

Node中的find()方法

/**
 * @param: h hash值
 * @param: k key值
 */
Node<K,V> find(int h, Object k) {
	//获取当前的Node类
    Node<K,V> e = this;
    if (k != null) {
        do {
            K ek;
            //Key值相等且hash相等,直接返回当前的Node类
            if (e.hash == h &&
                ((ek = e.key) == k || (ek != null && k.equals(ek))))
                return e;
        } while ((e = e.next) != null);
    }
    return null;
}

ConcurrentHashMap插入值

/**
 * 对外开放的put方法
 * @param: key key值
 * @param: value value值
 */
public V put(K key, V value) {
    //内部调用真正的赋值方法
    return putVal(key, value, false);
}

/**
 * 真正的赋值方法
 * @param: key key值
 * @param: value value值
 * @param: onlyIfAbsent 
 */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    //ConcurrentHashMap不允许null值,如果为null抛出异常
    if (key == null || value == null) throw new NullPointerException();
    //获取key的hash值
    int hash = spread(key.hashCode());
    //当为链表的时候,表示为链表的长度
    //当为红黑树时,固定为2,保证put后进行扩容检查,且不触发红黑树转换
    int binCount = 0;
    //这里是死循环
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh; K fk; V fv;
        //如果当前数组为空,则初始化ConcurrentHashMap
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        //i = (n - 1) & hash) 这里为获取key的index值
        //tabAt(tab, i = (n - 1) & hash)这里为寻找当前数组中,该index处的值
        //如果当前位置为空,直接插入
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            //使用CAS插入头元素,防止其他线程修改
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break;
        }
        //MOVED = -1
        //这里表示其他线程正在进行扩容
        else if ((fh = f.hash) == MOVED)
            //调用helpTransfer(),帮助扩容
            tab = helpTransfer(tab, f);
        //put方法默认传入的onlyIfAbsent为false,所以不走这段代码
        // check first node without acquiring lock(官方注释),检查头元素但不获取锁
        else if (onlyIfAbsent 
                 && fh == hash
                 && ((fk = f.key) == key || (fk != null && key.equals(fk)))
                 && (fv = f.val) != null)
            return fv;
        //这里为出现hash冲突,转换为树或链表
        else {
            //临时变量,存储旧元素
            V oldVal = null;
            //参数f加锁,f为原数组中的该位置的节点
            synchronized (f) {
                //cas重新获取当前数组中index位置的值,保证锁住的是第一个节点
                //判断是否与f相等,若不相等,代表有其他线程操作过这里了,停止本次操作,重新循环
                if (tabAt(tab, i) == f) {
                    //这里为链表
                    //因为第一次put值,ConcurrentHashMap要进行初始化,这里的判断是为了判断初始化/扩容是否完成了
                    if (fh >= 0) {
                        //这里是链表,赋值为1
                        binCount = 1;
                        //binCount改变为链表长度
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            //判断key是否相等
                            if (e.hash == hash && 
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                //如果key相等,先保存原始值
                                oldVal = e.val;
                                //onlyIfAbsent传入为false,所以这里永远为true
                                if (!onlyIfAbsent)
                                    //value值替换
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            //链表循环到最后一个元素,且key值不存在,在这里进行插入
                            if ((e = e.next) == null) {
                                //插入链表尾部
                                pred.next = new Node<K,V>(hash, key, value);
                                break;
                            }
                        }
                    }
                    //这里为红黑树
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        //红黑树默认为2
                        binCount = 2;
                        //调用新增树元素的方法
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) 
                            	!= null){
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                    else if (f instanceof ReservationNode)
                        throw new IllegalStateException("Recursive update");
                }
            }
            //只有为链表或红黑树的情况,才进入这里
            if (binCount != 0) {
                //链表长度大于等于8,转为红黑树
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    //扩容方法,此处用于检查是否需要扩容
    addCount(1L, binCount);
    return null;
}

ConcurrentHashMap扩容机制

addCount(),扩容方法

/**
 * 扩容方法
 * 这里的扩容方法分为两部分
 *   1.更新baseCount计数器
 *   2.扩容
 * @param: x baseCount增加的数量
 * @param: check 链表/树的长度
 */
private final void addCount(long x, int check) {
    CounterCell[] cs; long b, s;
    //更新baseCount
    //如果counterCells计数器为空,就运行后面的CAS进行baseCount++操作
    if ((cs = counterCells) != null ||
        //这里相当于做了baseCount++操作
        //如果CAS操作失败,就通过counterCells进行记录
        !U.compareAndSetLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell c; long v; int m;
        boolean uncontended = true;
        //如果counterCells为空
        if (cs == null || (m = cs.length - 1) < 0 || 
            //通过一个随机数(ThreadLocalRandom.getProbe() & m)来获取一个inedx
            //如果这个cs[index]位置的参数为空
            (c = cs[ThreadLocalRandom.getProbe() & m]) == null || 
             //CAS修改上面的随机出来的值,如果失败,说明出现并发情况
             !(uncontended = U.compareAndSetLong(c, CELLVALUE, v = c.value, v + x))) {
            //重试
            fullAddCount(x, uncontended);
            return;
        }
        //check为链表长度,如果小于1,则不考虑扩容
        if (check <= 1)
            return;
        //统计ConcurrentHashMap数量
        s = sumCount();
    }
    //check >= 0,则检查是否需要扩容
    if (check >= 0) {
        Node<K,V>[] tab, nt; int n, sc;
        //如果当前数组的数量(s)大于数组扩容的阈值(sizeCtl)
        //且当前数组不为空
        //且数组长度小于数组最大容量
        while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
               (n = tab.length) < MAXIMUM_CAPACITY) {
            //获取一个关于n的标识
            int rs = resizeStamp(n);
            //sc小于0,代表有其他线程正在扩容
            if (sc < 0) {
                //这里的1,4,5条件,都代表扩容已经完成,不需要再次扩容
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || 
                    sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || 
                    (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                //尝试帮助进行扩容,并使用CAS将正在执行的扩容数+1
                if (U.compareAndSetInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            //这里为没有线程正在扩容,尝试让自己成为第一个执行扩容的线程
            else if (U.compareAndSetInt(this,SIZECTL,sc,(rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
            //统计ConcurrentHashMap数量
            //这里用作判断是否要开启下一轮扩容
            s = sumCount();
        }
    }
}

transfer(),真正的扩容方法

/**
 * 真正的扩容方法
 * 用于移动或复制数据到新数组中
 * @param: tab 原数组
 * @param: nextTab 新数组
 */
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
    int n = tab.length, stride;
    //MIN_TRANSFER_STRIDE = 16
    //NCPU = Runtime.getRuntime().availableProcessors(),获取当前电脑的CPU处理器个数
	//这里将数组的(长度/8)/CPU核数,若结果小于16,就默认为16
    //为了让每个CPU处理同样的数量,避免数据分配不均
    if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
        stride = MIN_TRANSFER_STRIDE;
    //这里代表新的数组并未初始化,处于第一次扩容
    if (nextTab == null) {
        try {
            //n<<1 -> 扩容为2倍
            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
            //更新数组
            nextTab = nt;
        } catch (Throwable ex) {
            //如果出现异常,则扩容失败
            //数组容量sizeCtl直接使用最大值
            sizeCtl = Integer.MAX_VALUE;
            return;
        }
        //nextTable为成员变量,更新为刚才初始化的内容
        nextTable = nextTab;
        //transferIndex为扩容进度
        transferIndex = n;
    }
    //获取新数组的长度
    int nextn = nextTab.length;
    //创建一个特殊节点,表示这个节点正在进行扩容
    ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
    //控制是否继续处理下一个桶,true表示处理完当前的桶,可继续迁移下一组数据
    boolean advance = true;
    //用于控制扩容何时结束,也用于最后一个线程完成后,检查是否有遗漏的数据
    boolean finishing = false;
    //这里是死循环
    for (int i = 0, bound = 0;;) {
        Node<K,V> f; int fh;
        while (advance) {
            int nextIndex, nextBound;
            //i-1,判断是否大于bound
            //如果小于bound,则表示上次领取的任务完成了(上组数据迁移完成)
            //如果大于bound,或扩容完成了,则修改状态为false,结束操作
            if (--i >= bound || finishing)
                advance = false;
            //如果transferIndex<=0,表示数组分配的hash桶已经被分配完成了。
            else if ((nextIndex = transferIndex) <= 0) {
                //这里将i设置为-1,为后面使用,退出循环
                i = -1;
                advance = false;
            }
            //CAS修改transferIndex(length-区间值(stride)),留下的区间给其他线程使用
            else if (U.compareAndSetInt(this, TRANSFERINDEX, nextIndex,
                      nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {
                //线程可处理区间最小下标
                bound = nextBound;
                //对i赋值,i为线程可处理区间的最大下标
                i = nextIndex - 1;
                //防止数据没有复制
                advance = false;
            }
        }
        if (i < 0 || i >= n || i + n >= nextn) {
            int sc;
            if (finishing) {
                nextTable = null;
                table = nextTab;
                sizeCtl = (n << 1) - (n >>> 1);
                return;
            }
            if (U.compareAndSetInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                    return;
                finishing = advance = true;
                i = n; // recheck before commit
            }
        }
        else if ((f = tabAt(tab, i)) == null)
            advance = casTabAt(tab, i, null, fwd);
        else if ((fh = f.hash) == MOVED)
            advance = true; // already processed
        else {
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    Node<K,V> ln, hn;
                    if (fh >= 0) {
                        int runBit = fh & n;
                        Node<K,V> lastRun = f;
                        for (Node<K,V> p = f.next; p != null; p = p.next) {
                            int b = p.hash & n;
                            if (b != runBit) {
                                runBit = b;
                                lastRun = p;
                            }
                        }
                        if (runBit == 0) {
                            ln = lastRun;
                            hn = null;
                        }
                        else {
                            hn = lastRun;
                            ln = null;
                        }
                        for (Node<K,V> p = f; p != lastRun; p = p.next) {
                            int ph = p.hash; K pk = p.key; V pv = p.val;
                            if ((ph & n) == 0)
                                ln = new Node<K,V>(ph, pk, pv, ln);
                            else
                                hn = new Node<K,V>(ph, pk, pv, hn);
                        }
                        setTabAt(nextTab, i, ln);
                        setTabAt(nextTab, i + n, hn);
                        setTabAt(tab, i, fwd);
                        advance = true;
                    }
                    else if (f instanceof TreeBin) {
                        TreeBin<K,V> t = (TreeBin<K,V>)f;
                        TreeNode<K,V> lo = null, loTail = null;
                        TreeNode<K,V> hi = null, hiTail = null;
                        int lc = 0, hc = 0;
                        for (Node<K,V> e = t.first; e != null; e = e.next) {
                            int h = e.hash;
                            TreeNode<K,V> p = new TreeNode<K,V>
                                (h, e.key, e.val, null, null);
                            if ((h & n) == 0) {
                                if ((p.prev = loTail) == null)
                                    lo = p;
                                else
                                    loTail.next = p;
                                loTail = p;
                                ++lc;
                            }
                            else {
                                if ((p.prev = hiTail) == null)
                                    hi = p;
                                else
                                    hiTail.next = p;
                                hiTail = p;
                                ++hc;
                            }
                        }
                        ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                        (hc != 0) ? new TreeBin<K,V>(lo) : t;
                        hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                        (lc != 0) ? new TreeBin<K,V>(hi) : t;
                        setTabAt(nextTab, i, ln);
                        setTabAt(nextTab, i + n, hn);
                        setTabAt(tab, i, fwd);
                        advance = true;
                    }
                }
            }
        }
    }
}

sumCount(),统计map内的元素数量

/**
 * 统计ConcurrentHashMap的元素个数
 * 类似于size()
 * 这里统计counterCells的数量+baseCount
 */
final long sumCount() {
    CounterCell[] cs = counterCells;
    long sum = baseCount;
    if (cs != null) {
        for (CounterCell c : cs)
            if (c != null)
                sum += c.value;
    }
    return sum;
}

resizeStamp()

/**
 * 生成一个关于n的标识
 */
static final int resizeStamp(int n) {
    //RESIZE_STAMP_BITS = 16
    //(1 << (RESIZE_STAMP_BITS - 1) = (1 << 15),二进制位高16位为0,低16位为1
    return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}

ConcurrentHashMap的其他方法

containsKey()

/**
 * 判断当前key是否存在
 * @param: key key值
 */
public boolean containsKey(Object key) {
    //调用get()方法
    return get(key) != null;
}

containsValue()

/**
 * 判断当前value是否存在
 * @param: value value值
 */
public boolean containsValue(Object value) {
    //如果value为空,抛出异常
    if (value == null)
        throw new NullPointerException();
    Node<K,V>[] t;
    //当前数组不为空,才进行寻找
    if ((t = table) != null) {
        //Traverser 一个内部类,作用大致等用与迭代器
        Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
        for (Node<K,V> p; (p = it.advance()) != null; ) {
            V v;
            if ((v = p.val) == value || (v != null && value.equals(v)))
                return true;
        }
    }
    return false;
}

ConcurrentHashMap常见面试题

谈谈你理解的 Hashtable,讲讲其中的 get put 过程。ConcurrentHashMap同问。

  • 见上方

1.8 做了什么优化?

  • 取消了分段锁,采取了CAS+synchronized
  • 加入了红黑树

线程安全怎么做的?

  • 在操作数据的代码部分加锁(synchronized)
  • 采用锁升级技术

不安全会导致哪些问题?

  • 多线程同时操作出现的数据不一致问题
  • 如果数据结构为链表,可能出现死锁

如何解决?有没有线程安全的并发容器?

  • Collections.synchronizedMap(Map)创建线程安全的map
  • 使用HashTable
  • 使用ConcurrentHashMap

ConcurrentHashMap 是如何实现的?

  • 在HashMap的基础上加锁

ConcurrentHashMap并发度为啥好这么多?

  • 采用分段锁技术(CAS+synchronized)

1.7、1.8 实现有何不同?为什么这么做?

  • 1.7采用分段锁
  • 1.8采用CAS+synchronized

CAS是啥?

  • 乐观锁的一种实现方式
  • 当数据准备修改时,乐观的认为不会出现安全问题,当修改完准备保存时,将此时旧数据与修改前的旧数据进行比较,如果一致,则保存,否则放弃本次修改,重新读取流程。

ABA是啥?场景有哪些,怎么解决?

  • ABA问题:初始值为A,线程B将A改为B,线程C将B改为A,此时线程A就无法判断是否修改过了
  • 解决方法:加入版本号、时间戳等进行辅助

synchronized底层原理是啥?

synchronized锁升级策略

  • 偏向锁
  • CAS轻量级锁
  • 自旋锁
  • 重量级锁

快速失败(fail—fast)是啥,应用场景有哪些?安全失败(fail—safe)同问。

  • fail-fast:
    • java的一种安全机制
    • 当线程A遍历数据,线程B改变数据结构时,此时线程A访问数据,会抛出异常
    • 使用场景:java.util.concurrent包下的集合类都使用这种机制
  • fail-safe:
    • java的一种安全机制
    • 当线程A遍历数据,其他线程改变数据结构时,线程A无法检测到改变
    • 使用场景:java.util包下的集合类使用这种机制
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值