put方法

不能添加null值会报异常
计算key的hash,算出tab中的位置(index)
判断index的值是否为null
如果是null,直接new Node 放入

CAS 保证数据安全

比较交换
修改变量X=1,100线程 修改99
原始值(初始值) 1,最终需要修改的值 99
原子操作,CPU指令CAS是原子性的
判断当前线程,进行交换的时候,x的值是不是初始值1,如果是,赋值,如果不是,什么都不做
保证只有一个线程能,赋值成功,并没有线程的阻塞,线程切换开销
CAS有ABA的问题X的值是初始值 但是并不能保证这个值没被修改过

第一次put初始化

也是用CAS保证只有一个线程进行初始化 其他线程无法修改
当有一个线程在初始化的时候sc的值会变成-1当其他线程进来就会走另外一个分支让掉CPU时间给其他线程
当初始化完成之后有其他线程进来tab的值就不为空了直接返回tab 也就是已经初始化好的数组

扩容

为了提升集合的扩容的性能 map集合中其实是Node[]
线程1 分配数组中的0-5
线程2 分配数组中的6-10
线程2 分配数组中的11-15
这样互不影响还能提升性能
(fh = f.hash) == MOVED 当前的哈希值是moved也就是-1 就意味着整个hashmap在扩容
helpTransfer();方法不是扩容的入口这是帮助扩容的入口
里面会有一些条件判断 是否需要 进来的线程是否帮忙扩容

  1. final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
  2. Node<K,V>[] nextTab; int sc;
  3. if (tab != null && (f instanceof ForwardingNode) &&
  4. (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {//如果不满足这些条件就不用扩容
  5. int rs = resizeStamp(tab.length);
  6. //while里面是需要进行帮助扩容的条件
  7. while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) {
  8. //只有这个if中的条件有一个是true扩容就结束了
  9. if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
  10. sc == rs + MAX_RESIZERS || transferIndex <= 0)
  11. break;
  12. //下面这个条件才是真正开始扩容也是CAS CPU指令原子性
  13. if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
  14. transfer(tab, nextTab);
  15. break;
  16. }
  17. }
  18. return nextTab;
  19. }
  20. return table;
  21. }

transfer();方法

  • 第一个判断if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
    • NCPU 是本机电脑的线程数
    • n是当前数组长度
    • 条件成立stride=16
    • 就表示每个线程负责Node[]中的16个长度
  • 扩容过的节点会坐上标记 当其他线程进来进行扩容的时候就会判断是否有标记 有标记表明扩容过的不需要再进行扩容了

get方法中兼容了扩容中查询

如果不是,链表添加,红黑树添加, count(记录节点的长度),key是已有节点Node,那就覆盖value值
判断count值,是否大于树化的标准,转换数据结构,链表—>红黑树
保证map的查询性能,牺牲空间换取时间的做法。判断节点是否查询过当前大小阈值,扩容
size集合大小+1

Hashtable线程安全的
synchronized同步 get/put/remove
只有一个线程能访问这些方法