HashMap和ConcurrentHashMap


这个真的是一个老生常谈的问题了,大概记录一下就行了.附上手撕hashmap的简化版本。

手撕hashmap

public class HashMapDemo implements MyMap{
    private final int CAPACITY = 16;  //初始容量
    private int orignalSize = 16;   //实际容量
    Node[] table =new HashMapDemo.Node[CAPACITY];  //数组+链表的形式

    @Override
    public int size() {
        return orignalSize;
    }

    @Override
    public boolean isEmpty() {
        return orignalSize==0;
    }
    public int indexOf(int hash,int length){
        return hash%length;
    }
    @Override
    public Object put(Object key, Object value) {
        int hash  = hashCodes(key);
        int index = indexOf(hash,table.length);
        Node node = table[index];
        if(node == null) {
            table[index] = new Node(key,value);
            orignalSize++;
        }else {
            if(node.key.equals(key) || (node.hash == hash && node.key == key)){
                Object old = node.value;
                node.value = value;
                return old;
            }else {
                while (true){
                    if(node.next == null){
                        node.next = new Node(hash,key,value,null);
                        orignalSize++;
                        break;
                    }
                    if(node.next.hash ==hash&&node.next.key == key || node.next.key.equals(key)){
                        break;
                    }
                    node = node.next;
                }
            }
        }
        if(orignalSize == table.length){
            Node[] tableNew = new Node[table.length*2];
            System.arraycopy(tableNew,0,tableNew,0,table.length);
            table = tableNew;
        }
        return null;
    }

    private int hashCodes(Object key) {
        return key.hashCode();
    }

    @Override
    public Object get(Object key) {
        int hash = hashCodes(key);
        int indexOf = indexOf(hash,table.length);
        Node node = table[indexOf];
        while (node!=null){
            if(node.key.equals(key)&&node.hash == hash){
                return node.value;
            }
            node = node.next;
        }
        return null;
    }


    class Node<K,V> implements MyMap.Entry{
        int hash;
        private  K key;
        private V value;
        private HashMapDemo.Node<K, V> next;

        public Node(Object key, Object value) {
            this.key = (K) key;
            this.value = (V) value;

        }

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

        @Override
        public Object getKey() {
            return key;
        }

        @Override
        public Object getValue() {
            return value;
        }
    }
}

jdk1.7是头插法 1.8是尾插法
jdk1.8中
当链表超过8且数组长度(数据总量)超过64才会转为红黑树。当链表过长,则会严重影响HashMap的性能,红黑树搜索时间复杂度是O(logn),而链表是O(n)。
红黑树详解

hashmap是线程不安全的,为什么说hashmap是不安全的?

主要体现在两个点。put和resize;
如果说有两个线程A和线程B都在put值,但是没有同步锁。比如说线程A执行a=1,线程b执行a=2,这个时候两个线程线程执行到一半进入了线程等待状态,执行线程B,线程B执行结束以后线程A恢复了就会拿到一个旧的结果继续执行,导致最后的结果异常。我们线程A先执行线程B后执行,希望得到的结果是a=2,最后得到的结果是a=1.
在扩容情况下容易出现死锁问题;
ConcurrentHashMap 主要采用cas+synchronized技术保证,一个是在put值得时候使用cas机制保证值得准确获取。
另外一个就是采用synchronized修饰数组,保证数组在同一时段只有一个线程进入。

首先对于他的put resize等操作是没有锁同步的,也没有采用automic等安全的类。

Map<String, String> synchronizedMap = Collections.synchronizedMap(new HashMap<>());

或者使用concurrentHashMap 。
在初始化、新增和扩容过程中,ConcurrentHashMap 使用了一些机制来保证线程安全。
1.7版本的

初始化:
当创建一个新的 ConcurrentHashMap 实例时,会先创建一个初始大小的数组用于存储键值对。这个过程并不涉及并发操作,因此是线程安全的。

新增:
当执行新增操作时,ConcurrentHashMap 首先根据键的哈希值计算出该键应该存放在哪个 Segment 中,并对该 Segment 加锁。然后,在这个锁的保护下,新增操作可以安全地进行。其他线程访问不同的 Segment 不会受到影响,可以并发地进行读操作。只有当多个线程同时修改同一个 Segment 时,会出现竞争,需要等待锁的释放。

扩容:
ConcurrentHashMap 在内部维护了一个阈值,当哈希表中的元素个数达到阈值时,就会触发扩容操作。在扩容过程中,ConcurrentHashMap 需要创建一个更大的数组,并将原有的键值对重新分配到新的数组中。为了保证线程安全,ConcurrentHashMap 使用 CAS(Compare and Swap)操作和锁来控制并发访问。

具体的扩容过程如下:

创建一个新的数组,并且将原有的数组中的数据重新散列到新的数组中。
在扩容过程中,ConcurrentHashMap 会对每个 Segment 加锁,确保在扩容的同时,其他线程仍然可以并发地进行读操作。
扩容完成后,ConcurrentHashMap 更新内部的数组引用,并释放之前的数组。此时,新的数组已经可以提供更大的容量,可以继续安全地进行新增和读操作。
通过使用分段锁、CAS 操作以及合理的并发控制机制,ConcurrentHashMap 在初始化、新增和扩容过程中能够保证线程安全,从而支持高效的并发访问。
1.8版本的
主要采用cas+synchronized技术保证。
还是采用数组+链表的形式,但是对于数组 设置的使用cas设置。
对于便利链表使用synchronized形式。

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值