1.HashMap的个线数据机构详解
HashMap数据机构 = 数组 + 线性链表 + 红黑数(当数组长度>=8的时候链表会转红黑树)
数组查询时间复杂度 = O(1)
链表查询时间复杂度 = O (N)-> 红黑树诞生 -> 问题:当插入多的时候,很容易打破树的平衡性 -> 维持再平衡(左旋、右旋)以及重新作色
数组长度必须是2的指数次幂,默认情况下,hashmap进行初始化操作的时候,会把数组长度和加载因子传递进来,如果传进来的数组长度不是2的指数次幂,会自动改成大于这个容量最接近的2的指数次幂的数,它为什么要去这么做呢?保证hashmap在获取下标的时候,数组下标在容量大小的范围内,位运算的特特点,还有就是如果不是2的次幂,那么位运算的结果会导致和取模结果不等价。哇,对,就是这两个原因。
加载因子为什么是0.75?因为hashmap为了使数据能够更加的散列,避免产生大量的hash碰撞,以空间换时间的方式,使得数据能够分散到容器当中去。
hashmap8中为什么当链表长度>=8的时候,会将链表转换成红黑数?概率统计学的算法,【泊松分布】算法,这个算法,可以使得一个链表形成之后,重复的定位在同一个桶里的概率,会成指数级的越来越小。
hashmap8比7性能高的并不多,8%-10%
hashmap产生循环链的原因是什么?
扩容前 = 容器大小 = 4
线程1 执行 => Entry
线程2 第一次遍历,此时的 e 和 next 同时指向了B节点
线程2 执行第2次循环
此时,造成了链表中的数据,发生了体位互换了
线程1 此时被唤醒,继续开始执行,由于迁移的时候,链表中的数据发生了体位互换,当指针指来指去的时候,就形成了链表环
hashmap8 提供了4组指针,2组高位,2组地位指针,同时把高位和低位的指针断掉,转移到同数组下标的新容器当中去,这样做的好处就是,不会形成循环链表了,同时扩容的时候不需要重新hash取下标,大大提高了性能。这里面还有一个点就是高位在进行迁移的时候是数组下标+老数组大小,所以说,这也是为什么,数组长度必须是2的指数次幂。但hashmap8还不是线程安全的,hashmap没有考虑在初始化阶段保证线程安全的。
2.为何初始化容量要是2的指数幂?加载因子为什么是0.75
3.Java7的hashmap扩容死锁演示与环链分析
4.Jdk8的hashmap扩容优化,如何做到扩容无需rehash?
5.CurrentHashMap线程安全吗?为什么是分段锁?
红黑树:接近于平衡的二叉树(O(logn))
2.ConcurrentHashMap讲解
class Node(){
private String key;
private String value;
private Node next; //单项链表方式
}
class TreeNode(){
private Node left; //左子树
private Node right; //右子树
private boolean red; //是否红
}
1.先有数组:一定是在线程中执行的 main进行进行初始化的操作,单线程,多线程呢?
Node[] table = new Node(16);
resize() -> 对数组进行初始化static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//16
-> newCap = DEFAULT_INITIAL_CAPACITY; //设置大小
-> newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //设置阀值
-> Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //初始化操作
table = newTab; //其中的1<<4的意思就是1向右位移四位,在1的后面补上4个0,也就是2^4次方
2.key,value--Node();
Node对象put数组中
Node[] table = new Node(16); //单线程情况下,无论怎么折腾,都不会有问题的
T1:执行Task 16
T2:执行Task 32
在多线程的情况下操作,有可能和单线程的结果不一致。
所以:HashTable 在put/get的时候使用了synchroized(lock)保证线程安全,但是效率很低。
如何使用无锁化机制CAS,保证线程安全 -> Compare And Swap:比较并替换 -> 保证对某个线程操作安全
Cas是一种乐观锁的机制
int a = 内存当中有自己的值
T1 -> a 修改成b
T2 -> a 修改成c
ConcurrentHashMap使用了Cas无锁化机制保证了线程安全的机制
ConcurrentHashMap
//保证线程可见性
transient volatile Node<K,V>[] table;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
//线程1如果完成了初始化操作,线程2会进行一个让步,不会再进行初始化了
if ((sc = sizeCtl) < 0)
//让出现线程的执行权
Thread.yield(); // lost initialization race; just spin
//(U.compareAndSwapInt 无锁化线程保障
//操作数
// SIZECTL:内存当中的值
// sc:预期值
// -1:新值
// 初始化值操作只有一次会操作成功
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
//此时:SIZECTL = -1
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
//进行初始化
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
hash 函数
1.得到一个整形值 hash函数(key.hashCode()-> 高低16位运算)是一个随机值,保证了取模算法后取得的数组下标是
一定在这个容器大小的范围之内的。如何保证数组下标不一样,让数组均匀的分布在容器中,就是这个
hash函数的取值结构。
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//线程安全
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//判断当前头节点的hash值如果MOVED=-1的话,让当前线程暂停,去帮忙搬数组
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//同步数组索引的头节点,和分段锁是不一样的
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek
//替换key值
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
//按链表方式,放入一个最新的节点
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//红黑树放节点
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
//保证线程可见性、防止指令重排序
//使用cas保证原子性,比如初始化操作的时候
//数组索引的头节点上锁