• HashMap安全嘛? 不安全该怎么办【几种解决方法】【详细】


    为什么hashmap不安全

    可以找到hashmap的add方法看一下源码,源码:

     public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
    
    
    • 1
    • 2
    • 3
    • 4

    然后再找到里面的putval 方法

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    这里面的方法都没有加锁,进行同步,所以会发成并发的错误

    测试代码:

    import java.util.HashMap;
    import java.util.Map;
    import java.util.UUID;
    
    public class SetUnsefertyTest {
        public static void main(String[] args) {
            Map<String, String> map = new HashMap<>();
            for (int i = 1; i < 100; i++) {
                new Thread(() -> {
                    // 写入
                    map.put(UUID.randomUUID().toString().substring(0, 8), UUID.randomUUID().toString().substring(0, 8));
                    // 读出
                    System.out.println(map);
                }, String.valueOf(i)).start();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    请添加图片描述

    解决方法:

    方案1:使用HashTable(不采用)

    // 除了这一行之外,其他代码的和上面“1、HashMap不安全吗?”中的测试代码相同
    Map<String, String> map = new Hashtable<>();
    
    
    • 1
    • 2
    • 3

    测试代码:

    import java.util.HashMap;
    import java.util.Hashtable;
    import java.util.Map;
    import java.util.UUID;
    
    public class SetUnsefertyTest {
        public static void main(String[] args) {
            Map<String, String> map = new Hashtable<>();
            for (int i = 1; i < 100; i++) {
                new Thread(() -> {
                    // 写入
                    map.put(UUID.randomUUID().toString().substring(0, 8), UUID.randomUUID().toString().substring(0, 8));
                    // 读出
                    System.out.println(map);
                }, String.valueOf(i)).start();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    说明

    加了synchronized,读取效率太低,一次锁整张Hash表,相当于这个表是表锁,只要当前线程在读或者写,其他线程都不能进行操作,虽然解决了并发问题,但是相当于将并行操作变成了串行操作,另外在进行复合操作的时候也不是线程安全的,比如添加操作是判断是否存在和添加的联合操作,我们不期待这个过程在别人打扰,但是两个子操作中间是有可能被别人打扰的,可能我判断的时候没有这个值,但是在我执行添加操作之前被别人添加上了,然后我再去添加的时候发现有人已经添加了,这不就是添了个寂寞吗,说明添加操作就不是线程安全的

    方案2:使用Collections.synchronizedMap(new HashMap<>())(适合低并发小数据量的时候使用)

    // 除了这一行之外,其他代码的和上面“1、HashMap不安全吗?”中的测试代码相同
    Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
    
    
    • 1
    • 2
    • 3

    说明:

    可以把线程不安全的HashMap对象变成线程安全的对象,其实就是对HashMap中的每个方法上加synchronized

    测试代码:

    import java.util.*;
    
    public class SetUnsefertyTest {
        public static void main(String[] args) {
    //        Map map = new Hashtable<>()
            
            Map<String,String> map = Collections.synchronizedMap(new HashMap<>()) ; 
            
            
            for (int i = 1; i < 100; i++) {
                new Thread(() -> {
                    // 写入
                    map.put(UUID.randomUUID().toString().substring(0, 8), UUID.randomUUID().toString().substring(0, 8));
                    // 读出
                    System.out.println(map);
                }, String.valueOf(i)).start();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    方案3:使用ConcurrentHashMap代替HashMap(适合多线程高并发大数据量的时候使用)

    // 除了这一行之外,其他代码的和上面“1、HashMap不安全吗?”中的测试代码相同
    Map<String, String> map = new ConcurrentHashMap<>();
    
    
    • 1
    • 2
    • 3

    说明:

    在JDK1.7中,ConcurrentHashMap里面使用的是锁分段机制,默认ConcurrentLevel分段级别是16,也就是说默认有16个段Segment段,每个段上有一个锁,每个段后面都是一个哈希表,这样每次就有16个锁了,我们每次操作的是不同的锁,这样比HashTable中的1个锁要好多了,至少不是串行操作了

    在jdk1.8中,ConcurrentHashMap取消了分段锁机制(锁分段机制也是一样的意思,都可以用,没有区别),底层使用CAS算法,CAS算法根本没有使用锁,它使用的是while循环判断,如果修改成功那就结束while循环,否则只要依然有CPU时间片,那就继续执行修改操作,直到执行成功为止,由于JVM为Unsafe类中的CAS方法写成汇编指令,虽然CAS中涉及到获取比较交换三个操作,但是这三个操作是原子操作,中途不会被打断,所以CAS操作是原子性的,并且没有锁,效率更高,由于一直在while循环中,所以会减少线程上下文切换的消耗,但是循环也会带来一些时间开销

  • 相关阅读:
    服务器集群配置LDAP统一认证高可用集群(配置tsl安全链接)-centos9stream-openldap2.6.2
    SAP 请求
    C++深度优先搜索(DFS)算法的应用:树中可以形成回文的路径数
    15.Session和Cookie
    JS中字符串与ASCII码相互转换,前端如何解决秘钥非明文存储
    jmeter学习记录
    ubuntu 22.04版本修改时区的操作方法
    JS之instanceof方法手写
    [激光原理与应用-38]:《光电检测技术-5》- 光学测量基础 - 光调制
    java报错NoClassDefFoundError: Could not initialize class
  • 原文地址:https://blog.csdn.net/weixin_54046648/article/details/128174911