多线程 - worldgreen/freamwork-test GitHub Wiki

concurrentHashMap 1.8

  1. sizeCtl 含义
    • -1表示table正在初始化
    • -n表示有 n - 1个线程在扩容
    • 为正
      • 如果table没有初始化,表示表需要初始化的大小
      • 如果初始化完成,表示table容量,通常为table大小 * 0.75
  2. put 函数 CAS+synchronized
    • 如果table 为空,初始化table tab = initTable();
    • 如果对应位置为空,cas加入到空节点地方,不容加锁, getObjectVolatile 得到节点,table 是volatile 但 table[i] 不是,每次可的最新数据, compareAndSwapObject ,如果成功break 失败自旋
    • 如果第一个节点hash值为MOVED,正在扩容,当前线程帮助扩容,helpTransfer(tab, f);
    • 对第一个节点加锁,并修改或插入 synchronized (f)
    • 如果 节点数 >= 8 变为红黑树
  3. 扩容:
    • table 大小 * 2
    • 从后往前遍历,对有元素的节点加锁转换。
    • 转换完后把第一个节点变为 ForwardingNode,表示本节点扩容完成。
  4. 定义
    • 直接使用transient volatile HashEntry<K,V>[] table保存数据,用table元素作为锁,实现对每一行数据加锁,减少冲突概率
    • 将数组加链表 -> 数组 + 链表 + 红黑树
  5. 初始化
    • table为空进行初始化
    • 如果sizectl < 0 有线程在初始化,yield 让出CPU
    • 初始化的时候让 sizectl = -1;
  6. size
    • 通过volatile变量的basesize记录元素个数,添加,删除元素通过addCount方法更新 basecount
    • 通过累加baseCount和CounterCell数组中的数量,即可得到元素的总个数

concurrentHashMap 1.7

  1. 定义
    • 每一个segment都是一个hashEntry<>[] table,每一个元素都是hashEntry的单向队列,减少并发冲突,不属于同一个segment节点并发操作
    • segment 继承ReentrantLock可作为互斥锁使用
  2. put
    • put的时候 通过hash找到segments 数组中的 segment,如果为 null,cas初始化,在加锁执行segment的put操作。
  3. size
    • 连续计算元素个数,最多3次,如果连续两次不同,给每个segment加锁,在计算一次

LinkedHashMap

  1. 结构
    • 继承 HashMap
    • accessOrder 控制元素的顺序
      • false:按 put 元素的顺序存放
      • true:按访问的顺序放
    • entry,有前后两个指针,是数组和双向链表的结合体
  2. put方法
    • 调用hashmap的put方法
      • 如果找到对应的key,改变value,执行 afterNodeaccess
      • 如果没有插入到最前面,插入后执行 afterNodeInsertion
  3. afterNodeAccess 中如果 accessOrder = true,把当前元素插入到双向链表结尾
  4. get 方法,如果 accessOrder = true,get执行的时候会 执行 afterNodeaccess,将节点放在双向链表结尾

aqs同步框架

  1. ReentrantLock ReentrantReadWriteLock Semaphore CountDownLatch . 都有 Sync,都实现了 tryAcquire 方法,定义获取锁的方式,而其他内容,如加入等待线程链表就让 aqs实现,并调用了 aqs 的 acquire 和 relase方法
  2. state 线程通过这个变量的值来判断自己阻塞还是进入临界区,比如公平锁,如果state = 0,没线程抢占,看自己是不是等待队列中第一个线程,如果是,ExclusiveOwnerThread设为当前线程,如果不是加入队列中
  3. lockSuppoprt.pack/unpack 阻塞或唤醒线程, 实现一个线程对另一个线程精确控制,当ExclusiveOwnerThread运行完后,会LockSupport.unpark(s.thread);
  4. 无锁链表实现的所有阻塞线程,形成一个阻塞队列
  5. 提供了 acquire,acquireInterruptibly, 让子类实现 tryAcquire
  6. acquireQueued屏蔽中断,doAcquireInterruptibly响应中断,前者被中段继续死循环拿锁,后者返回并取消acquire
  7. 独占锁,只有一个线程拿到资源,释放锁的时候也只唤醒一个线程
  8. 共享锁,可以有多个线程拿到资源,释放的时候唤醒队列中的全部线程,都同时在去拿锁。
  9. semaphore acquire 只要 state > 0 就能拿到锁,否则加到阻塞队列中, relase时候,把 state + ,唤醒全部线程
  10. countDownLunch await 看 state是否为0,如果不为0、countDown还没减到0,把当前线程放在队列中,如果减到0到时候会唤醒队列中全部元素。

concurrentLinkedQueue

单向无锁链表实现的, 和 AQS 的 无锁阻塞队列比较类似,从尾入队,从头出队,用cas实现的无锁队列

  private static class Node<E> {
        volatile E item;
        volatile Node<E> next;

CyclicBarrier

  1. 结构
  • ReentrantLock + condition(trip) + state 变量
  1. await
  • 加锁, state--, 如果 state == 0,唤醒其他线程,并恢复 state,否则 trip.await 阻塞自己

ScheduledThreadPoolExecutor

  1. 方法
  • schedule(Callable callable, long delay, TimeUnit unit) 非周期性的
  • scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 固定频偏, 执行时间小于period
  • scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) 固定间隔执行
  1. 内部实现
  • DelayedWorkQueue 优先队列,按时间排序,一个 lock,一个condition available
  • take 加锁,如果队列为空 available.await();等待,否则 等待头的一段时间 available.awaitNanos(delay);
  • 非周期执行的时候直接放入队列中 super.getQueue().add(task);
  • 周期执行, 执行完后把 task的时间加一个周期,重新放队列中

强,软,弱,虚引用

  • 强引用:A a = new A() 只要引用存在,不会垃圾回收
  • 软引用:内存溢出前进行回收,实现缓存功能,内存足够的情况下通过软引用取值,当内存不足的时候回收这部分对象
  • 弱引用:第二次垃圾回收时回收,检测是否被垃圾回收器标记
  • 虚引用:垃圾回收时回收,检测是否从内存中删除

偏向锁,轻量级,自旋锁,重量级锁

  • 自旋锁:持有锁的线程在很短时间内释放资源,等待锁的线程不需要内核态和用户态转换,只要等一等
  • 自适应自旋:自选时间不固定,有上次在同一锁上自选时间锁拥有者状态决定,如果在同一个锁上,自旋刚刚获得锁,本次会自旋更长时间
  • 轻量级锁:对象头(mark word)运行时数据,大部分锁,不存在竞争
    • 加锁:如没被锁定(01),在当前栈建锁记录,存储锁对象mark word,cas将对象mark work更新为指向栈记录,如果更新成功,锁标志为轻量级锁(00),如果失败,膨胀为重量级锁(10)
    • 解锁,如果对象头指向栈空间,cas替换,如果替换失败,说明有线程竞争锁,唤醒阻塞线程
  • 偏向锁 消除cas,整个过程只有一个线程请求锁
    • 虚拟机启用了偏向锁,锁第一次被线程获取时,将对象头锁标志位 01,偏向锁,同时cas把线程ID放到mark word 中,如果线程再次获取锁,不需同步。 看,l
⚠️ **GitHub.com Fallback** ⚠️