线程-线程不安全

发布时间 2023-11-27 23:44:37作者: 轻寒

线程不安全

例如:线程不安全的HashMap
在多线程环境下,使用HashMap进行put操作会引起死循环,
导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。例如,执行以下代码会引起死循环。

final HashMap<String, String> map = new HashMap<> (2);
Thread t = new Thread (() -> {
for (int i = 0; i < 10000; i++) {
new Thread (() -> map.put (UUID.randomUUID ().toString (), ""), "ftf" + i).start ();
}
}, "ftf");
t.start();
t.join();

1.HashMap在并发执行put操作时会引起死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,
一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。
2.JDK1.8之前,为了提高rehash的速度,冲突链表是使用头插法,因为头插法是操作速度最快的,
找到数组位置就直接找到插入位置了,头插法在多线程下回引起死循环
3.JDK1.8之后开始加入红黑树,当链表长度大于8时链表就会转换成红黑树,这样就大大提高了在冲突链表查找的速度,同时因为链表的长度不可能大于8,
链表在rehash的消耗就小很多,所以JDK1.8使用尾插法也避免了死循环问题。

数据丢失问题

HashMap部分源码:

if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有hash碰撞则直接插入元素

这是jdk1.8中HashMap中put操作的主函数, 注意这行代码,如果没有hash碰撞则会直接插入元素。


假设线程A和线程B同时进行put操作,刚好这两条不同的数据hash值一样,并且该位置数据为null,所以这线程A、B都会进入这行代码中。


假设一种情况,线程A进入后还未进行数据插入时挂起,而线程B正常执行,从而正常插入数据,然后线程A获取CPU时间片,此时线程A不用再进行hash判断了,问题出现:线程A会把线程B插入的数据给覆盖,发生线程不安全。
总结:

在jdk1.7中,在多线程环境下,扩容时会造成环形链或数据丢失。
在jdk1.8中,在多线程环境下,会发生数据覆盖的情况。