您现在的位置是:主页 > news > 大型门户网站建设美丽/网站维护的内容有哪些

大型门户网站建设美丽/网站维护的内容有哪些

admin2025/6/7 13:58:13news

简介大型门户网站建设美丽,网站维护的内容有哪些,wordpress 双语主题,电脑公司网站设计写在前面1、HashMap本身是线程不安全的(不管哪个jdk版本),所以请不要在并发访问的场景下直接使用HashMap。2、如果在并发访问的场景下,建议采用concurrentHashMap。问题原因1、头插法:我们知道,在jdk1.8之前,hashMap的…

大型门户网站建设美丽,网站维护的内容有哪些,wordpress 双语主题,电脑公司网站设计写在前面1、HashMap本身是线程不安全的(不管哪个jdk版本),所以请不要在并发访问的场景下直接使用HashMap。2、如果在并发访问的场景下,建议采用concurrentHashMap。问题原因1、头插法:我们知道,在jdk1.8之前,hashMap的…

写在前面

1、HashMap本身是线程不安全的(不管哪个jdk版本),所以请不要在并发访问的场景下直接使用HashMap。

2、如果在并发访问的场景下,建议采用concurrentHashMap。

问题原因

1、头插法:

我们知道,在jdk1.8之前,hashMap的put操作,或扩容操作,针对hash冲突时采用的是拉链法(将冲突的对象以链表的形式串联起来),新加入的冲突元素将会插到原有链表的头部。基本结构如图:1bae80d72790daea472050a7b7af2121.png

设计思想:局部性原理,当时设计HashMap的大叔采用头插法而没有采用尾插法有一点考虑是性能优化,认为最近put进去的元素,被get的概率相对较其他元素大,采用头插法能够更快得获取到最近插入的元素。

但头插法的设计有一个特点,就是扩容之后,链表上的元素顺序会反过来,这也是死循环的一个重要原因。

2、在并发环境下使用非线程安全的类

这个前面也说了,正确使用HashMap也就不会有这个问题了。

具体成环的情况分析

我们看一下HashMap的put操作:

    public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}

modCount++;
// 关注元素插入过程
addEntry(hash, key, value, i);
return null;
}
    void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry>K,Vvalue, e);
if (size++ >= threshold)
// 扩容
resize(2 * table.length);
}

void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
// ...省略部分
Entry[] newTable = new Entry[newCapacity];
// 原有数据迁移
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}

重点关注上面的transfer方法: 它会读取原有hashmap中的元素,通过do...while的形式迁移到新的hashmap中:

    void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
// 这里是下面讲的重点
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}

我们以一个实例说明这个问题:77d0f88379bedf415cc65363c5b9ed15.pngcd1cf5f556dd3ace830d322ee19ecc75.png79602b647bf8bc2205e6b4aa0bec188e.pngd9091f630465f0cbd8f90604ff967263.png

6f6ae9f5638350b3706c92396aca9a24.png

问题答疑

1、有同学对图中的红色字有所疑惑,认为线程B对链表的操作,线程A怎么会看到呢?不是有线程可见性问题吗?

首先得理解线程可见性的原因是因为有cpu缓存,在线程执行之前,读取了操作数,在操作过程中操作数都在CPU缓存中,在线程没有将操作数写入主存之前,线程中对操作数的修改则对于其他线程是不可见的。

而在hashMap扩容的过程中,线程操作的是堆中的对象,线程持有的是对对象的引用。引用就是一个地址,对引用的修改就是对堆中对象的修改。线程B的操作对于线程A的操作是透明的,所以线程A能看到线程B对链表的修改。