黑马程序员技术交流社区

标题: 【并发】为什么HashMap是线程不安全的? [打印本页]

作者: 逆风TO    时间: 2020-3-25 16:05
标题: 【并发】为什么HashMap是线程不安全的?
经常会看到说HashMap是线程不安全的,ConcurrentHashMap是线程安全的等等说法,不禁有个疑问,什么是线程安全?什么样的类是线程安全的?

1.什么是线程安全性(what)
线程安全定义,最核心是正确性,

正确性:多个线程访问某个类,不管怎么调度这些线程,其代码中不需要额外的同步或协同(synchronized),这个类依然有正确的行为。

线程安全类封装了必要的同步机制,在任何调用对象的public方法或读写pulic字段field时,都不会破坏对象的一致性状态(符合不变性条件或后验条件)

为什么一段看似正确的代码,多线程并发执行就会不正确了呢??大概有以下3种问题




[Java] 纯文本查看 复制代码
public class DoubleCheckSingleton {
    /**
     * 使用volatile,在多线程场景下,确保在判断null时,对所有线程可见
     */
    private static volatile DoubleCheckSingleton uniqInstance;
    /**
     * 构造器私有,防止外部实例化该类
     */
    private DoubleCheckSingleton() {}
    /**
     * 静态方法实例化,由于在类内部,可以调用构造器
     */
    public static DoubleCheckSingleton getInstance(){
        if (null == uniqInstance) { // 此处判断需要可见性volatile
            synchronized (DoubleCheckSingleton.class) {
                if (null == uniqInstance) {
                    //延迟初始化
                    uniqInstance = new DoubleCheckSingleton();
                }
            }
        }
        return uniqInstance;
    }
}

2.如何分辨一个类是否线程安全?(HOW)
3.为什么hashmap不安全 why

最简单的线程安全集合实现,可以发现是通过以上线程安全共享的方式实现安全。

[Java] 纯文本查看 复制代码
public class SafeSet {
    // final
    private final Set<Integer> myset = new HashSet<>(); //饿汉  一旦创建不会发布出去

    // synchronized 同步访问
    public synchronized void addInteger(Integer p){
        myset.add(p);
    }
    public synchronized boolean containsInteger(Integer p) {
        return myset.contains(p);
    }
}

3.1 插入HashMap.put

如下,put方法有很多先检查后操作(需要原子性执行)的代码,由于put()和putVal()代码没有同步,所有的线程可以执行到此处代码,

(1)table==null? 初始化线程A执行check操作后,发生线程切换,B也check table==null操作,A、B都会resize()更新table,产生更新丢失!

[Java] 纯文本查看 复制代码
if ((tab = table) == null || (n = tab.length) == 0)//(1)线程切换
            n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)//(2)线程切换
    tab = newNode(hash, key, value, null);

(2)tab==null?  A 线程和 B 线程计算出相同的哈希值对应了相同的数组位置,此时该位置还没数据,然后对同一个数组位置,两个线程会同时 写入新的头结点,那B的写入操作就会覆盖 A 的写入,造成 A 的写入操作丢失。


3.1.1 HashMap 在扩容的时候

  HashMap 插入后超过阈值会触发扩容resize操作,new一个新容量cap的数组,对原数组的键值对重新进行计算hash并写入新数组,然后指向新数组。

[Java] 纯文本查看 复制代码
 if (++size > threshold)// 线程切换
    resize();       

当A、B线程同时进来,检测到总数量超过阈值的时候就会同时触发 resize 操作,各自生成新的数组并 rehash 后赋给该 map 底层的数组,结果最终只有最后一个线程生成的新数组被赋给该 map 底层,其他线程的均会丢失。


JDK1.7甚至在扩容时并发有可能形成链表回路,导致后续操作遍历链表时有死循环!具体请戳


3.2 HashMap 在删除数据的时候

  删除这一块可能会出现两种线程安全问题,

[Java] 纯文本查看 复制代码
if ((tab = table) != null && (n = tab.length) > 0 &&(p = tab[index = (n - 1) & hash]) != null) {
    Node<K,V> node = null, e; K k; V v;
    // 找到对应节点
    if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))//切换线程(2)
        node = p;
    else if ((e = p.next) != null) {
        if (p instanceof TreeNode)
            node = ((TreeNode<K,V>)p).getTreeNode(hash, key); // 红黑树查找
        else { // 链表查找
            do {
                if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
                    node = e;
                    break;
                }
                p = e;
            } while ((e = e.next) != null);
        }
    }
    if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v))))  {
        if (node instanceof TreeNode)
            ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); //删除树节点
        else if (node == p)
            tab[index] = node.next;        // 删除链表头
        else
            p.next = node.next; // 删除链表中间
        ++modCount;
        --size;
        afterNodeRemoval(node);
        return node;
    }
}
return null;

线程A判断得到了指定的数组位置i并进入了循环,此时,线程B已经删掉位置i数据了,然后线程A那边就没了。但是删除的话,没了倒问题不大,只是A返回的就是null

当A、B线程同时操作同一个数组位置的时候,也都会先取得现在状态下该位置存储的头结点,然后各自去进行计算操作,之后再把结果写会到该数组位置去,其实写回的时候可能其他的线程已经就把这个位置给修改过了,就会覆盖其他线程的修改。

还有很多检查后操作的情况不能保证原子性操作,且有检查的时候不能保证可见性,检查到的值有可能是失效数,执行过程中不能保证其一致性状态,也不能保证符合程序设计者的预期!


且不安全地发布了内部字段,如keyset等,容易被其它现在在类外不可预期地修改!!


转自CSDN原文链接:https://blog.csdn.net/sarafina527/article/details/105040594




作者: 大安    时间: 2020-3-27 10:28

感谢楼主的分享  棒棒棒
作者: 1467584    时间: 2020-4-7 10:23
666666666666666666666666666
作者: kdhdjdj    时间: 2020-4-7 10:23
666666666666666666666666666666666
作者: 王锦    时间: 2020-4-7 10:30


感谢楼主的分享  棒棒棒
作者: 你不爱我    时间: 2020-4-7 10:45
厉害了                       
作者: sdjadyhm    时间: 2020-4-7 10:55
66666666666666666
作者: 逆风TO    时间: 2020-4-7 10:57
感谢分享  棒棒哒
作者: hongping    时间: 2020-4-7 11:07

感谢分享  棒棒哒
作者: hongping    时间: 2020-4-7 11:13

感谢分享  棒棒哒
作者: daoqin    时间: 2020-4-7 11:29

作者: 哦嗨呦    时间: 2020-4-7 11:33
好人一生平安
作者: Emmmmm~    时间: 2020-4-7 11:38


                                                                                                  
键盘敲烂,月薪过万
作者: 我是小圆圆    时间: 2020-4-7 11:52
可以的,奥利给!!!
作者: 孙丽    时间: 2020-4-7 11:54
66666666666666666666666
作者: 孙丽    时间: 2020-4-7 11:54
66666666666666666666666
作者: 殷凯老师    时间: 2020-4-7 14:06
66666666666666
作者: manyihang    时间: 2020-4-7 14:26
666666666666
作者: jsnoob    时间: 2020-4-7 14:31
加油加油加油!!!
作者: 大安    时间: 2020-4-7 15:11
键盘敲烂,月薪过万
作者: 大安    时间: 2020-4-7 15:11

可以的,奥利给!!!
作者: 大安    时间: 2020-4-7 15:12

可以的,奥利给!!!
作者: 章鱼顶呱呱    时间: 2020-4-8 10:10
66666666666666666666
作者: 耙丫丫    时间: 2020-4-9 08:42
6666666666666666666666
作者: lvxinvip    时间: 2020-4-9 09:33

作者: longyu3    时间: 2020-4-9 09:54
棒棒哒 加油 完美入行
作者: mydorling11    时间: 2020-4-9 09:58
6666666666666666666666666666
作者: duanshaobo    时间: 2020-4-9 10:26
在这春暖花开的季节
作者: 半个程序员    时间: 2020-4-9 14:06


可以的,奥利给!!!

作者: json0314    时间: 2020-4-9 14:52
加油哦.加油哦!
作者: 举个栗子    时间: 2020-4-9 14:59
666666666666666666666666666666
作者: 123木头人555    时间: 2020-4-9 15:21
666666666666666666666666666666666666666666666666
作者: 小公举    时间: 2020-4-9 15:31
666666666666666666
作者: 小公举    时间: 2020-4-9 15:32
66666666666666
作者: 小公举    时间: 2020-4-9 15:33
66666666666666666666
作者: yujq    时间: 2020-4-9 18:29
66666666666666666
作者: 零度☆黎明    时间: 2020-4-9 23:18
不错, 不错 .................. ..................
作者: zplxwl    时间: 2020-4-10 00:34
666666666666666666666666
作者: 大智叔叔    时间: 2020-4-10 09:26
加油加油加油!!!
作者: 九月丫    时间: 2020-4-10 09:30
666666666666666666666666666666666666666666666
作者: lzq123    时间: 2020-4-10 09:39
666666666666666666666666666666666
作者: 我爱我1022    时间: 2020-4-10 09:41

作者: 影@子~    时间: 2020-4-10 10:05

作者: 竹竹竹竹    时间: 2020-4-10 10:08
6666666666666666666666666666666666
作者: 王微    时间: 2020-4-10 10:19

作者: 霍尔    时间: 2020-4-10 10:57
66666666666666666666666
作者: hello!!!    时间: 2020-4-10 11:11

作者: zhaosongzhi    时间: 2020-4-10 11:24
66666666666666
作者: 黑马程序员啊    时间: 2020-4-10 13:03
66666666666666666666666666666
作者: 雨落轻舟    时间: 2020-4-10 18:38
6666666666666666666666666666666666666666666666666666
作者: 素问    时间: 2020-4-12 22:16
谢谢分享,加油~~~~!!!!




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2