2、HashMap中的常量( 六 )


线程1被挂起后并没有完成扩容,因此线程2进入后同样会执行扩容方法,并且线程2将扩容方法执行完成,已经成功被扩容!但是线程1中e、next所指向的元素没有改变,线程2扩容后两个指向如图所示:
线程1中还没有开始扩容,所以还只是一个长度为4的空数组,如上图左侧所示 。table是已经被线程2扩容后的桶数组,如上图右侧图所示 。
随后开始执行e.next=[i]、[i]=e,因为此时是空数组,因此e.next==null,此时[i]=e,随后执行e=next让指针e与指针next都指向同一个元素key(7),如下图所示:
第二次循环执行=e.next,next指向key(3):
执行e.next=[i]、[i]=e,将key(7)用头插法插入到第三号桶中:继续执行e=next,此时e再一次指向了key(3)
第三次循环执行=e.next,此时next == null,执行e.next=[i]也就是将左侧线程1中的key(3)指向线程1中的头结点key(7),这样形成了循环链表!继续执行[i]=e并没有产生变化 。随后e=next让e为空结束循环 。
此时扩容已经结束,可以看到桶数组中存放的链表已经变成了循环链表,任何的操作都可能导致死循环 。这也就是为什么在jdk8.0之后的扩容方式改为了尾插法 。问题2:与有什么区别? 1、继承的类不同
继承自而继承自,两者均实现了Map接口 。是一个相当古老的类,官方已经废弃了这个类的使用 。
2、线程不安全,线程安全
不是一个线程安全的数据结构,在前面也说到多线程情况下jdk1.7的可能出现循环链表导致查询时出现死循环 。同时因为没有数据上锁机制因此put方法可能覆盖其他线程的修改 。总体来说线程不安全 。
通过关键字实现线程安全,但是这种实现方法需要大量的上锁、解锁工作,性能较差,因此也不推荐使用保证线程安全 。
3、不允许存放空值
允许存放一个null的key值,value值没有null值限制,但是键值对均不能为空 。
4、计算Hash的方式不同
计算Hash值的方法:
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
计算Hash值的方法:
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
5、扩容方式不同 的扩容是原始容量的两倍的扩容方式是原始容量的两倍+1 6、解决Hash冲突的方式不同