集合框架核心知识点——线程安全JUC

(一)Collections.synchronized实现原理

1、Collections.synchronizedList实现原理

(1)首先是Collections.synchronizedList方法需要传入一个List的实现类,根据其实现类是否为RandomAccess,
否则进入内部类SynchronizedList进行初始化;

public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list) :
            new SynchronizedList<>(list));//初始化成员变量和锁对象
}

(2)其次无论实现类是啥,最终都是通过SynchronizedList的父类SynchronizedCollection来初始化mute和list成员变量;

SynchronizedCollection类:
final Collection<E> c;
final Object mutex;
SynchronizedCollection(Collection<E> var1) {
    this.c = (Collection)Objects.requireNonNull(var1);//初始化成员变量
    this.mutex = this;//当前对象为锁对象
}

(3)最后使用JU集合类调用被Collections.synchronizedList包装后的add、get、remove等方法,
实际上会被添加synchronized锁保证线程安全,最终还是会调用不安全的方法。

SynchronizedCollectionpublic boolean add(E e) {
        synchronized (mutex) {return c.add(e);}//加锁并调用不安全方法
    }
    public boolean remove(Object o) {
        synchronized (mutex) {return c.remove(o);}//加锁并调用不安全方法
    }
SynchronizedListpublic E get(int var1) {
        synchronized(this.mutex) {
            return this.list.get(var1);//加锁并调用不安全方法
        }
    }

总结:

使用线程不安全的集合类如ArrayListLinkedList等来被Collections.synchronizedList包装后,
只是对不安全线程方法添加了synchronized保证线程安全,实际底层还是调用不安全集合类方法。

2、Collections.synchronizedMap实现原理

原理:
使用JU集合类调用Collections.synchronizedMap方法后对m和mute进行初始化,
同时调用基础方法都添加了synchronized方法保证线程安全,然后调用底层不安全方法。
SynchronizedMap:
private final Map<K,V> m; // Backing Map
final Object mutex; // Object on which to synchronize

SynchronizedMap(Map<K,V> m) {
    this.m = Objects.requireNonNull(m);//初始化成员变量
    mutex = this;//设置当前对象为锁对象
}

public V get(Object key) {
    synchronized (mutex) {return m.get(key);}//加锁并调用不安全方法
}

public V put(K key, V value) {
    synchronized (mutex) {return m.put(key, value);}//加锁并调用不安全方法
}
public V remove(Object key) {
    synchronized (mutex) {return m.remove(key);}//加锁并调用不安全方法
}

总结:

使用线程不安全的集合类如HashMapLinkedHashMap等来被Collections.synchronizedMap包装后,
只是对不安全线程方法添加了synchronized保证线程安全,实际底层还是调用不安全集合类方法。

3、Collections.synchronizedSet实现原理

(1)首先是Collections.synchronizedSet方法需要传入一个Set的实现类,调用内部类SynchronizedSet去初始化;

public static <T> Set<T> synchronizedSet(Set<T> s) {
    return new SynchronizedSet<>(s);
}

(2)通过SynchronizedSet的父类SynchronizedCollection来初始化mute、c成员变量;

SynchronizedCollection类:
    final Collection<E> c;
    final Object mutex;
    SynchronizedCollection(Collection<E> var1) {
        this.c = (Collection)Objects.requireNonNull(var1);//初始化成员变量
        this.mutex = this;//当前对象为锁对象
    }

(3)最后使用JU集合类调用被Collections.synchronizedSet包装后的add、remove等方法,
实际上会被添加synchronized锁保证线程安全,最终还是会调用不安全的方法。

SynchronizedCollectionpublic boolean add(E e) {
        synchronized (mutex) {return c.add(e);}//加锁并调用不安全方法
    }
    public boolean remove(Object o) {
        synchronized (mutex) {return c.remove(o);}//加锁并调用不安全方法
    }

总结:

使用线程不安全的集合类如HashSetTreeSet等来被Collections.synchronizedSet包装后,
只是对不安全线程方法添加了synchronized保证线程安全,实际底层还是调用不安全集合类方法。

(二)CopyOnWrite实现原理

1、CopyOnWriteArrayList实现原理(读写分离、写时复制机制)、使用场景

实现原理

CopyOnWriteArrayList底层仍然采用数组存储,在get不加锁直接读取索引值,而add在读取数据时是加了非公平锁(ReentrantLock),
防止其他线程同时写入,获取锁执行权后将旧数组拷贝到新容量的数据组(+1),进行设置新值,最后将新数组设置到原数组中,释放锁。

核心源码

(1)get方法

/**
* 说明:get方法未加锁,是因为add方法添加数据时,采用复制新数组并重新设置数据的方式,
* get方法获取不需要加锁,原因是获取中的值,只是查询索引值,不涉及改数据
*/
public E get(int var1) {//根据索引获取指定值
    return this.get(this.getArray(), var1);//调用私有多参数方法
}
private E get(Object[] var1, int var2) {
    return var1[var2];//基于存储数组返回指定索引值
}

(2)add方法

/**
* 说明:add方法需要加锁,涉及到数据增加,先获取锁的执行权,然后将旧数组以+1的容量
* 将原数组复制到新数组,设置新值和将新数组赋值给原数组
*/
public boolean add(E var1) {
    ReentrantLock var2 = this.lock;//得到锁
    var2.lock();//获取锁执行权
    boolean var6;//默认false
    try {
        Object[] var3 = this.getArray();//获取当前存储数组
        int var4 = var3.length;//目前已存入的数量
        Object[] var5 = Arrays.copyOf(var3, var4 + 1);//基于当前数组复制一个新的数组并+1
        var5[var4] = var1;//设置值
        this.setArray(var5);//将新数组设置到当前数组
        var6 = true;//返回结果
    } finally {
        var2.unlock();//释放锁
    }
    return var6;//返回执行结果
}

使用场景

根据写时复制、读写分离的原理,适用于多读的场景(读不需要加锁),共享读,互斥写,保证数据一致性.即读多写少的场景。

2、为什么没有ConcurrentArrayList?

基于ArrayList类型的数据结构,单纯的数组结构,对于在高并发情况下,是无法像HashMap那样开发出ConcurrentHashMap原因如下:

1ConcurrentArrayList的数据结构是单纯的数组,不像ConcurrentHashMap是采用数组+链表(+红黑树);2ConcurrentArrayList存储数组要么锁全部list,要么不锁,ConcurrentHashMap确可以对桶进行加锁,分段实现数据一致性,
提高了并发性;3ConcurrentArrayList无法开发成通用的并发ArrayList,在做了很多限制的情况下,产生了读读共享和读写互斥的CopyOnWriteArrayList,来提高并发性。

3、CopyOnWriteArraySet实现原理和使用场景

实现原理

通过构造器实例化并发set,其构造器内实际new CopyOnWriteArrayList,为CopyOnWriteArraySet内部成员al赋值,
其内部方法均是通过CopyOnWriteArrayList al变量来调用CopyOnWriteArrayList内的方法,唯一不同的方法如下核心源码。

核心源码

CopyOnWriteArraySet类:
     public boolean add(E e) {
        return al.addIfAbsent(e);//通过内部成员调用CopyOnWriteArrayList中的方法
    }
CopyOnWriteArrayList:
    public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();//获取当前数组
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);//判断当前e是否已存在,不存在则添加,存在则直接返回
    }
    //添加数据方法
    private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;//获取锁
        lock.lock();//获取锁的执行权
        try {
            Object[] current = getArray();//重新回去当前数组
            int len = current.length;//最新数组长度
            if (snapshot != current) {//判断在获取锁前后的数组是否一致
                int common = Math.min(snapshot.length, len);//不一致的情况,取较小的数组长度
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))//判断e是否在多线程操作环境中是否被添加
                        return false;
                if (indexOf(e, current, common, len) >= 0)//检验e是否已存在
                        return false;
            }
            Object[] newElements = Arrays.copyOf(current, len + 1);//e不存在数组中并新建数组(+1)
            newElements[len] = e;//设置值
            setArray(newElements);//将新数组设置到存储数组
            return true;//返回执行结果
        } finally {
            lock.unlock();//释放锁
        }
    }

使用场景

根据set的无重复性和CopyOnWriteArrayList的有序性,可以得出适用于有序且不能重复的数据存储场景。

(三)ConcurrentHashMap实现原理

1、实现原理

1ConcurrentHashMap底层结构为Node数据+链表+红黑树+CAS+synchronized;2)put整体原理:
	第一步是key-value均不能为null;
	第二步扰乱算法;
	第三步为是否初始化即初始化时会将sizeCtl置为-1,初始化完毕会设置为阈值;链表查找或红黑树查找或键值替换操作都会伴随着
	synchronized锁住桶位,且多处替换操作均会使用cas操作如桶位设置、sizeCtl设置值、多线程扩容helpTransfer等。
PS:
    sizeCtl各阶段值含义:-1代表正在初始化 0代表程序默认值 >0代表阈值 -N代表正在扩容的线程(N的二进制取低16-1即为扩容线程,原因是在扩容中左移16+2)
    key-value键值对中的hash值各阶段含义:-1代表正在迁移fwd >0代表key经过扰乱算法后的hash -2代表该桶位为TreeBin节点(作用是快速区分链表)

2、扩容及优化

(1)扩容

扩容场景:

第一个是在put中binCount值是否大于等于树化阈值8,treeifyBin方法中判定数组长度是否小于64,来确定是否扩容tryPresize(n << 1);
第二个是在put中由addCount(1L, binCount)方法中判定是数据量是否大于阈值来确定transfer扩容迁移。

扩容步骤:

第一步是先根据数组大小是否超过阈值;
第二步是超过阈值sizeCtl后,再根据sizeCtl是大于0,还是小于0做处理,等于0是初始化时候会考虑;
    大于0进行-N(resizeStamp(n))转化后并U.compareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2);
    小于0进行U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)转化即多线程进入扩容;
第三步是调用transfer进行扩容迁移,其中位运算扩容,多线程并发分配迁移范围、
    推进标记advance、finishing和fwd节点来确定迁移完成,链表的查找、高低位的整体迁移等。

(2)优化

(1)多线程并发扩容,利用NCPU来确定扩容步长;
(2)使用cas来改变sizeCtl值,利用-N的二进制低位16位值M-1来确定多少个线程同时扩容;
(3)在迁移过程中使用hash值与旧数组大小按位与操作,确定每个桶位上的最右边几个连续不需要改的链表节点,直接链接,减少循环的hash操作。

3、ConcurrentHashMap与HashTable对比?

1)存储结构底层不一样;并发Map是采用Node数组+链表+红黑树+CAS+Synchronized存储和并发,HashTable是使用synchronized来保
证线程安全。
(2)扩容机制不同;并发Map是可以使用多线程并发扩容,且翻倍扩容且容量都是2的幂,HashTable是根据容量翻倍+1进行扩容,非必要2的幂。
(3Key-Value都是不能为null,并发MapHashTable性能强。

4、ConcurrentHashMap是如何在保证并发安全的同时提高性能?

1)线程安全:使用CAS+Synchronized来保证数据一致性;2)提高性能:利用Cas来控制sizeCtl值变换,根据不同值来进行多线程扩容和迁移,对链表和红黑树节点时利用synchronized保证具体
桶位数据安全,以及不同的hash值(-1,-2和大于0)来代表并发Map是迁移中、红黑树和链表状态。

5、ConcurrentHashMap是如何让多线程同时参与扩容?

1)put方法中定位到当前桶时,根据当前桶位的Node节点Hash值等于MOVED(-1)时则进行helpTransfer进行多线程扩容迁移;2)迁移过程:先使用resizeStamp方法获取一个扩容戳,将其左移16位并+1(此时sizeCtl利用CAS是一个非常小的-N),此时低16-1
则代表此时多少个线程正在扩容,然后根据数组长度确定新的数组和迁移,其中涉及到NCPU和迁移步长、fwd和迁移标志、完成标志来保证迁移。

6、核心源码

(1)put方法(暴露API)

  //提供给外部调用的put方法
  public V put(K key, V value) {
      return putVal(key, value, false);//调用内部put(或putVal)
  }

(2)putVal方法(核心)

//实现并发Map添加值
 final V putVal(K key, V value, boolean onlyIfAbsent) {
     if (key == null || value == null) throw new NullPointerException();//key-value不允许为空
     int hash = spread(key.hashCode());//扰乱算法并保证是正数范围内
     int binCount = 0;
     for (Node<K,V>[] tab = table;;) {//for循环遍历tab
         Node<K,V> f; int n, i, fh;
         if (tab == null || (n = tab.length) == 0)//判断tab是否被初始化
             tab = initTable();//初始化tab
         else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//利用扰乱算法后的得到的hash值与数组容量-1进行按位与运算确定桶索引
             if (casTabAt(tab, i, null,
                 new Node<K,V>(hash, key, value, null)))//利用cas判断当前桶位置是否为null,为空则将新的node设置到该位置,否则进去其它分支
                 break;//设置成功,结束for循环            }
         else if ((fh = f.hash) == MOVED)//判断当前f的hash值是否为MOVED(-1代表扩容中正在被迁移)
             tab = helpTransfer(tab, f);//进行多线程帮助迁移方法
         else {//进入此分支,则代表该桶位置有值,需要被遍历,可能是链表,也可能是红黑树结构
             V oldVal = null;
             synchronized (f) {//使用synchronized对该桶加锁(分段锁思想)
                 if (tabAt(tab, i) == f) {//判断锁住的对象为该桶的第一个位置
                     if (fh >= 0) {//hash值大于等于0,则表明是链表
                         binCount = 1;//计数器,用于是否需要被转为红黑树
                         for (Node<K,V> e = f;; ++binCount) {//遍历该链表
                             K ek;
                             if (e.hash == hash && ((ek = e.key) == key ||
                                  (ek != null && key.equals(ek)))) {//判断是否同一个键
                                 oldVal = e.val;
                                 if (!onlyIfAbsent)//替换判断,concurrentHashMap的onlyIfAbsent默认为false
                                     e.val = value;//设置新的值
                                 break;
                             }
                             Node<K,V> pred = e;//用于链接新node节点的临时变量
                             if ((e = e.next) == null) {//遍历结束条件
                                 pred.next = new Node<K,V>(hash, key,value, null);//将新的node入链表
                                 break;//结束循环
                             }
                         }
                     }
                     else if (f instanceof TreeBin) {//判断该桶第一个node是否为红黑树
                         Node<K,V> p;
                         binCount = 2;
                         if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,//红黑树遍历并判断是否被替换的key-value
                                                        value)) != null) {
                             oldVal = p.val;//获取原key对应的value
                             if (!onlyIfAbsent)//被替换条件,concurrentHashMap的onlyIfAbsent默认为false
                                 p.val = value;//设置新的值
                         }
                     }
                 }
             }
             if (binCount != 0) {//链表分支中计算器
                 if (binCount >= TREEIFY_THRESHOLD)//判断是否链表长度大于等于默认阈值8
                     treeifyBin(tab, i);//树化方法
                 if (oldVal != null)//替换值是否被赋值
                     return oldVal;//替换成功后返回旧值
                 break;
             }
         }
     }
     addCount(1L, binCount);//对count进行计算的方法
     return null;
 }

(3)spread:扰乱算法

//扰乱算法:对key的hashcode进行右移16位再取异或,最后与HASH_BITS(0x7fffffff=2148473847)按位与运算,
//保证其值在Integer范围内
static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}

(4)initTable:初始化方法(核心)

//初始化table方法
 private final Node<K,V>[] initTable() {
      Node<K,V>[] tab; int sc;
      while ((tab = table) == null || tab.length == 0) {
          if ((sc = sizeCtl) < 0)//判断是否在被初始化,-1代表初始化
              Thread.yield(); //释放线程执行权即其它线程正在初始化
          else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
          //利用cas进行比较交换,基于this对象的SIZECTL偏移量的值,对期望值为sc相同,对此偏移量地址进行-1设置
              try {
                  if ((tab = table) == null || tab.length == 0) {//判断table是否被其它线程初始化
                      int n = (sc > 0) ? sc : DEFAULT_CAPACITY;//判断是否有初始容量值,此时的sc是初始容量
                      @SuppressWarnings("unchecked")
                      Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];//初始化数组
                      table = tab = nt;//赋值
                      sc = n - (n >>> 2);//计算下次扩容阈值,此时sc为阈值
                  }
              } finally {
                  sizeCtl = sc;//将阈值赋值给sizeCtl,以后的扩容变量
              }
              break;//退出while循环
          }
      }
      return tab;//返回初始化完成的数组
  }

(5)helpTransfer:多线程迁移方法(核心)

//帮助table进行迁移
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
    Node<K,V>[] nextTab; int sc;
    //判断当前桶位置是否为ForwardingNode(迁移时使用其作为临时类型,其hash值为-1)并对nextTab进行赋值为ForwardingNode
    if (tab != null && (f instance of ForwardingNode) &&
        (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
        int rs = resizeStamp(tab.length);//获取一个当前数组长度的扩容戳
        while (nextTab == nextTable && table == tab &&
               (sc = sizeCtl) < 0) {//判断扩容后的数组大小一致和旧的数组一致并且sizeCtl为负值(-N扩容标志)
            /**
            * 判断sc进行sizeCtl赋值后进行右移16位与扩容戳对比,不一致,则可以进行sc重新赋值,
            * 继续判断是否上限和数组是否还有未被分配来扩容的桶
            */
            if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                sc == rs + MAX_RESIZERS || transferIndex <= 0)
                break;//放弃此线程扩容帮助
            if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {//cas进行设置sizeCtl为sc+1
                transfer(tab, nextTab);//进行帮助扩容阶段
                break;
            }
        }
        return nextTab;//返回帮助扩容table后的新数组
    }
    return table;//返回table
}

(6)resizeStamp:获取扩容戳方法

   /**
   * Integer.numberOfLeadingZeros该方法的作用是返回无符号整数i的最高非0位前面的0的个数,
   * 包括符号位在内:如果n为负数,这个方法将会返回0,符号位为1。
   * 如16的二进制表示为0000 0000 0000 0000 0000 0000 0001 0000返回27
   * RESIZE_STAMP_BITS为16
   */
 static final int resizeStamp(int n) {
      //返回一个基于当前n值的一个特定值(最高位非0个数与特殊值进行或运算的值)即扩容戳
      return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
  }

(7)transfer:数据迁移方法(核心)

//多线程同时扩容转移方法
 private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
     int n = tab.length, stride;
     //根据内核(数组长度右移三位和内核确定)和默认步长来确定最小步长
     if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
         stride = MIN_TRANSFER_STRIDE; //最小步长为默认的16
     if (nextTab == null) {//初始化nextTab
         try {
             @SuppressWarnings("unchecked")
             Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];//创建新扩容后的数组
             nextTab = nt;//赋值
         } catch (Throwable ex) {      // try to cope with OOME
             sizeCtl = Integer.MAX_VALUE;//异常情况下赋值为最大值
             return;
         }
         nextTable = nextTab;//对nextTable成员变量赋值(新扩容的空数组)
         transferIndex = n;//转移下标设置原数组的最大下标
     }
     int nextn = nextTab.length;//新数组的长度
     ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);//使用fwd来作为临时扩容变量
     boolean advance = true;//转移进度标志
     boolean finishing = false; //转移是否完成标志
     for (int i = 0, bound = 0;;) {//转移下标和边界值
         Node<K,V> f; int fh;//此桶的第一节点和hash值
         //while只是确定i和bound以及transferIndex的赋值
         while (advance) {//while循环控制转移
             int nextIndex, nextBound;//开始转移的索引和边界值
             if (--i >= bound || finishing)//从后往前转移
                 advance = false;//代表转移完毕,用于跳出循环
             else if ((nextIndex = transferIndex) <= 0) {//赋值当前线程负责的转移区域并判断是否转移结束
                 i = -1;//用于控制if分支
                 advance = false;//用于控制while循环
             }else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,
                 nextBound = (nextIndex>stride?nextIndex-stride:0))) {//cas对transferIndex进行计算并赋值
                 bound = nextBound;//当前线程负责的扩容边界值
                 i = nextIndex - 1;//if分支处判断语句是大于等于,故要-1
                 advance = false;//终止while循环,继续走for循环,此时的i和bound都被重新赋值,开始for循环
             }
         }
         if (i < 0 || i >= n || i + n >= nextn) {//
             int sc;
             if (finishing) {//完成标志
                 nextTable = null;//变量置空,用于GC回收
                 table = nextTab;//将旧数组替换为新数组
                 sizeCtl = (n << 1) - (n >>> 1);//计算新扩容阈值
                 return;
             }
             if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {//cas对sizeCtl-1设置,前面有方法对sizeCtl+1过,需要还原
                 if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)//扩容完sc-2刚好与后面条件相等
                     return;
                 finishing = advance = true;//设置完成标志
                 i = n; //提交前检查是否全部迁移完,下面的分支MOVED
             }
         }
         else if ((f = tabAt(tab, i)) == null)//桶验证是否为null
             advance = casTabAt(tab, i, null, fwd);//将tab数组下标为i的结点设置为fwd结点,表示迁移
         else if ((fh = f.hash) == MOVED)//-1代表正在迁移,说明已经被迁移过了,fwd节点
             advance = true; //继续进行迁移中
         else {
             synchronized (f) {
                 if (tabAt(tab, i) == f) {//防止f发生变化
                     Node<K,V> ln, hn;//高低位链表(用于迁移)
                     if (fh >= 0) {//大于等于0代表链表
                         int runBit = fh & n;//判断当前n的从右到左第一个为1的位运算,高位是1或者0
                         Node<K,V> lastRun = f;//当前桶位给lastRun
                         for (Node<K,V> p = f.next; p != null; p = p.next) {//遍历链表
                             int b = p.hash & n;//节点的hash与旧容量位运算,确定高位值
                             /** 两处场景:
                             * (1)当runBit(fh & n)高位为0时,下面if只有b的高位为1才进入并将1赋值给runBit,将高位头链表给到lastRun
                             * (2)当runBit(fh & n)高位为1时,下面if只有b的高位为0才进入并将0赋值给runBit,将低位位头链表给到lastRun
                             * 不断的(1)和(2)高低位变换,直到链表遍历完最后一次变动,记录下不需要变动的lastRun
                             * 作用:仅仅是确定后面链表(链表从后到前)不需要改变的第一个节点
                             */
                             if (b != runBit) {
                                 runBit = b;
                                 lastRun = p;
                             }
                         }
                         //将不需要改变的第一个节点lastRun记录下,判断记录的是高位还是低位子链表
                         if (runBit == 0) {//等于0,则上述lastRun记录的是低位
                             ln = lastRun;
                             hn = null;
                         }else {//等于1,则上述lastRun记录的是高位
                             hn = lastRun;
                             ln = null;
                         }
                         //遍历链表,根据高低位进行新数组中存储
                         for (Node<K,V> p = f; p != lastRun; p = p.next) {
                             int ph = p.hash; K pk = p.key; V pv = p.val;
                             if ((ph & n) == 0)//低位放入低链表
                                 ln = new Node<K,V>(ph, pk, pv, ln);//可能会引用上面记录的lastRun赋值的ln
                             else//高位放入高链表
                                 hn = new Node<K,V>(ph, pk, pv, hn);//可能会引用上面记录的lastRun赋值的hn
                         }
                         setTabAt(nextTab, i, ln);//对低位整体迁移
                         setTabAt(nextTab, i + n, hn);//对高位整体迁移
                         setTabAt(tab, i, fwd);//标记当前桶已被迁移完成
                         advance = true;//进行--i与bound验证,用于while循环
                     }else if (f instanceof TreeBin) {//红黑树结构
                         TreeBin<K,V> t = (TreeBin<K,V>)f;
                         TreeNode<K,V> lo = null, loTail = null;
                         TreeNode<K,V> hi = null, hiTail = null;
                         int lc = 0, hc = 0;
                         for (Node<K,V> e = t.first; e != null; e = e.next) {//遍历从第一个节点开始
                             int h = e.hash;
                             TreeNode<K,V> p = new TreeNode<K,V>(h, e.key, e.val, null, null);//基于e创建一个TreeNode节点
                             if ((h & n) == 0) {//低位
                                 if ((p.prev = loTail) == null)//判断低位头节点
                                     lo = p;//低位头节点
                                 else
                                     loTail.next = p;//低位后续节点连接
                                 loTail = p;//递推低位用于连接
                                 ++lc;//低位数量自增
                             }else {
                                 if ((p.prev = hiTail) == null)//判断高位头结点
                                     hi = p;//高位头节点
                                 else
                                     hiTail.next = p;//高位后续节点连接
                                 hiTail = p;//递推高位用于连接
                                 ++hc;//高位数量自增
                             }
                         }
                         /**
                         * 低位是否需要解树;
                         * untreeify解为链表,TreeBin构造方法是变为红黑树;
                         * hc != 0 在低位中判断是否高位有变化,无变化则直接访问第一个节点t即桶位元素;
                         */
                         ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin<K,V>(lo) : t;
                         /**
                         * 高位是否需要解树;
                         * untreeify解为链表,TreeBin构造方法是变为红黑树;
                         * lc != 0 在低位中判断是否高位有变化,无变化则直接访问第一个节点t即桶位元素;
                         */
                         hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin<K,V>(hi) : t;
                         setTabAt(nextTab, i, ln);//低位迁移
                         setTabAt(nextTab, i + n, hn);//高位迁移
                         setTabAt(tab, i, fwd);//标记该桶位为fwd,代表迁移过
                         advance = true;
                     }
                 }
             }
         }
     }
 }

(8)untreeify:解树方法(核心)

//解树操作
 static <K,V> Node<K,V> untreeify(Node<K,V> b) {
     Node<K,V> hd = null, tl = null;//头尾节点
     for (Node<K,V> q = b; q != null; q = q.next) {//遍历TreeNode类型的链表
         Node<K,V> p = new Node<K,V>(q.hash, q.key, q.val, null);//构建普通node
         if (tl == null)//确定头节点判断
             hd = p;//头结点赋值,用于返回
         else
             tl.next = p;//节点连接
         tl = p;//递推连接节点并保证if中条件不成立
     }
     return hd;//返回头节点
 }

(9)TreeNode:树化构造方法(核心)

 //树化为红黑树结构
 //此时的b还只是单链表的红黑树
 TreeBin(TreeNode<K,V> b) {
     super(TREEBIN, null, null, null);//将节点的hash值设置为TREEBIN(-2)
     this.first = b;//第一个节点引用TreeNode链表
     TreeNode<K,V> r = null;
     for (TreeNode<K,V> x = b, next; x != null; x = next) {//遍历b
         next = (TreeNode<K,V>)x.next;//递推x
         x.left = x.right = null;
         if (r == null) {//设置根节点
             x.parent = null;//根节点无需父节点
             x.red = false;//黑色
             r = x;//设置节点值TreeNode
         }else {
             K k = x.key;//当前节点的key
             int h = x.hash;//当前节点的value
             Class<?> kc = null;
             for (TreeNode<K,V> p = r;;) {//遍历基于r根节点的树结构
                 int dir, ph;
                 K pk = p.key;
                 if ((ph = p.hash) > h)//遍历节点与当前节点判断hash大小
                     dir = -1;//当前节点小,则走左边,dir为-1
                 else if (ph < h)//当前节点大于遍历节点
                     dir = 1;//当前节点大,则走右边,dir为1
                 else if ((kc == null &&(kc = comparableClassFor(k)) == null) ||
                          (dir = compareComparables(kc, k, pk)) == 0)//判断hash值相等
                     dir = tieBreakOrder(k, pk);//tieBreakOrder该方法会得到一个不是0的值即1和-1
                 //插入节点的父节点
                 TreeNode<K,V> xp = p;
                 if ((p = (dir <= 0) ? p.left : p.right) == null) {//根据dir的值进行左右判断
                     x.parent = xp;//设置父节点
                     if (dir <= 0)//左子树判定
                         xp.left = x;//当前节点为左节点
                     else
                         xp.right = x;//当前节点为右节点
                     r = balanceInsertion(r, x);//平衡树结构,包括左旋和右旋,暂不赘述(篇幅过长),前面的HashMap文章中已阐述
                     break;
                 }
             }
         }
     }
     this.root = r;//根节点为r,平衡树结构
     assert checkInvariants(root);//检查此root树是否符合红黑树五大特性
 }
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值