日常记录——多线程与高并发—并发容器Map实现类Hashtable、ConcurrentHashMap、ConcurrentSkipListMap

一、简介

1.Hashtable:Entry<K,V>的数组加链表结构,默认长度11,扩容因子0.75,每次扩容后长度为(之前的长度*2)+1。基于synchronized方法保证线程安全。
put方法:计算索引值,判断应该放在哪个索引下,如果这个索引下有相同key,则覆盖,返回旧值,否则返回null。

public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

get方法:计算当前哈希值,和应该在哪个索引下,遍历索引,如果hash和key匹配到,返回对应value,否则返回null。

 public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }

2.ConcurrentHashMap:Node<K,V>的数组加链表||树状结构,默认长度16,扩容因子0.75,每次扩容后长度为之前的长度*2。在索引下添加当前Entry<K,V>。基于cas、synchronized和volatile保证线程安全。
put方法:根据 key 计算出 hashcode 。判断是否需要进行初始化。根据 key 定位出的 Node,如果为空使用 CAS写入。如果当前处于扩容阶段,则参与扩容,否则使用 synchronized 锁写入数据。如果当前链表长度大于8则转换为树状结构。

public V put(K key, V value) {
        return putVal(key, value, false);
    }
final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                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);
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                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) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

get方法:计算哈希,定位索引,如果是在主干上,直接返回value,如果是树那就按照树的方式获取值。否则按照链表的方式遍历获取值。

public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        int h = spread(key.hashCode());
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            else if (eh < 0)
                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;
    }

3.ConcurrentSkipListMap:Node<K,V>的链表结构。有序,基于cas和volatile实现线程安全。
结构示例如下:例如查找哈希值为3的节点,先确定在头结点和哈希值为5这个节点内,然后在确定在2-5内,然后定位到3。
在这里插入图片描述
put方法:首先找到插入位置即找到key的前继节点和后继节点。新建节点z,并将新节点z插入,更新跳表。

 	public V put(K key, V value) {
        if (value == null)
            throw new NullPointerException();
        return doPut(key, value, false);
    }
    private V doPut(K kkey, V value, boolean onlyIfAbsent) {
        Comparable<? super K> key = comparable(kkey);
        for (;;) {
            Node<K,V> b = findPredecessor(key);//查找前驱数据节点
            Node<K,V> n = b.next;//后继节点
            for (;;) {
                if (n != null) {
                    Node<K,V> f = n.next;
                    //后继两次读取不一致,重试
                    if (n != b.next)               // inconsistent read
                        break;
                    Object v = n.value;
                    //后继数据节点的值为null,表示该数据节点标记为已删除,删除该数据节点并重试。
                    if (v == null) {               // n is deleted
                        n.helpDelete(b, f);
                        break;
                    }
                    //b节点被标记为已删除,重试
                    if (v == n || b.value == null) // b is deleted
                        break;
                    int c = key.compareTo(n.key);
                    if (c > 0) {//给定key值大于当前,后移,继续寻找合适的插入点
                        b = n;
                        n = f;
                        continue;
                    }
                    if (c == 0) {//找到 onlyIfAbsent 入参就位true  设置值成功
                        if (onlyIfAbsent || n.casValue(v, value))
                            return (V)v;
                        else
                            break; // restart if lost race to replace value
                    }
                }
                //新建数据节点
                Node<K,V> z = new Node<K,V>(kkey, value, n);
                //绑定到下一节点
                if (!b.casNext(n, z))
                    break;         // restart if lost race to append to b
                int level = randomLevel();//随机的索引级别
                if (level > 0)
                	//建立索引
                    insertIndex(z, level);
                return null;
            }
        }
    }
    private void insertIndex(Node<K,V> z, int level) {
        HeadIndex<K,V> h = head;
        int max = h.level;
 
        if (level <= max) {//索引级别已经存在,在当前索引级别以及底层索引级别上都添加该节点的索引
            Index<K,V> idx = null;
            for (int i = 1; i <= level; ++i)//首先得到一个包含1~level个索引级别的down关系的链表,最后的idx为最高level索引 
                idx = new Index<K,V>(z, idx, null);
            addIndex(idx, h, level);//新增索引
        } else { // 新增索引级别
        	//索引级别加一
            level = max + 1;
            Index<K,V>[] idxs = (Index<K,V>[])new Index[level+1];
            Index<K,V> idx = null;
            //更新索引
            for (int i = 1; i <= level; ++i)
                idxs[i] = idx = new Index<K,V>(z, idx, null);
            HeadIndex<K,V> oldh;
            int k;
            for (;;) {
                oldh = head;
                int oldLevel = oldh.level;
                //更新头索引级别
                if (level <= oldLevel) { // lost race to add level
                    k = level;
                    break;
                }
                HeadIndex<K,V> newh = oldh;
                Node<K,V> oldbase = oldh.node;
                //更新head索引
                for (int j = oldLevel+1; j <= level; ++j)
                    newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
                if (casHead(oldh, newh)) {
                    k = oldLevel;
                    break;
                }
            }
            addIndex(idxs[k], oldh, k);
        }
    }

get方法:找到key的前继节点,然后找到前继节点的后继节点,如果key的值和前继节点的后继节点相等则返回value。

public V get(Object key) {
        return doGet(key);
}
private V doGet(Object okey) {
    Comparable<? super K> key = comparable(okey);
    for (;;) {
        // 找到对应的节点
        Node<K,V> n = findNode(key);
        if (n == null)
            return null;
        Object v = n.value;
        //不为null  返回value
        if (v != null)
            return (V)v;
    }
}
private Node<K,V> findNode(Comparable<? super K> key) {
    for (;;) {
        // 找到key的前继节点
        Node<K,V> b = findPredecessor(key);
        // 找到前继节点的后继节点
        Node<K,V> n = b.next;
        for (;;) {
            // 如果n为null,直接返回null。
            if (n == null)
                return null;
            //前继节点的后继节点的后继节点
            Node<K,V> f = n.next;
            // 如果两次读取到的后继节点不同 重新遍历。
            if (n != b.next)                // inconsistent read
                break;
            Object v = n.value;
            // 如果前继节点的后继节点值为null 删除前继节点的后继节点并 前继节点的后继节点的后继节点绑定到前继节点
            if (v == null) {                // n is deleted
                n.helpDelete(b, f);
                break;
            }
            //前继节点被删除  跳出
            if (v == n || b.value == null)  // b is deleted
                break;
            // 若n是当前节点,则返回n。
            int c = key.compareTo(n.key);
            if (c == 0)
                return n;
            // 前继节点的后继节点的key小于当前key,则说明跳表中不存在key对应的节点,返回null
            if (c < 0)
                return null;
            // 向下继续查找。
            b = n;
            n = f;
        }
    }
}

二、代码代码举例

说明:创建两个线程分别向Hashtable、ConcurrentHashMap、ConcurrentSkipListMap、HashMap容器对象添加10000个元素,最后打印容器长度。Hashtable结果:20000,ConcurrentHashMap结果:20000,ConcurrentSkipListMap:20000,HashMap:<20000(添加元素方法不是同步的,连两个线程放的key进行哈希运算后确定放的位置相同并且都为null情况下,t1线程放完,t2会覆盖t1存放的值)。

  public static void main(String[] args) {
//        HashMap<UUID,String> map = new HashMap<>();
//        Hashtable<UUID,String> map = new Hashtable<>();
//        ConcurrentHashMap<UUID,String> map = new ConcurrentHashMap<>();
        ConcurrentSkipListMap<UUID,String> map = new ConcurrentSkipListMap<>();
        new Thread(() ->{
            for (int i = 0; i < 10000; i++) {
                map.put(UUID.randomUUID(),"1");
            }
        }).start();
        new Thread(() ->{
            for (int i = 0; i < 10000; i++) {
                map.put(UUID.randomUUID(),"1");
            }
        }).start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(map.size());
    }

三、Hashtable、ConcurrentHashMap、ConcurrentSkipListMap对比

1.Hashtable:使用synchronized保证线程安全,锁定方法,效率低。
2.ConcurrentHashMap:使用cas、synchronized、volatile保证线程安全,效率较高。
3.ConcurrentSkipListMap:有序、跳表结构,基于cas、volatile保证线程安全、效率较高。

猜你喜欢

转载自blog.csdn.net/weixin_43001336/article/details/107272762