多线程 - worldgreen/freamwork-test GitHub Wiki
- sizeCtl 含义
- -1表示table正在初始化
- -n表示有 n - 1个线程在扩容
- 为正
- 如果table没有初始化,表示表需要初始化的大小
- 如果初始化完成,表示table容量,通常为table大小 * 0.75
- put 函数 CAS+synchronized
- 如果table 为空,初始化table tab = initTable();
- 如果对应位置为空,cas加入到空节点地方,不容加锁, getObjectVolatile 得到节点,table 是volatile 但 table[i] 不是,每次可的最新数据, compareAndSwapObject ,如果成功break 失败自旋
- 如果第一个节点hash值为MOVED,正在扩容,当前线程帮助扩容,helpTransfer(tab, f);
- 对第一个节点加锁,并修改或插入 synchronized (f)
- 如果 节点数 >= 8 变为红黑树
- 扩容:
- table 大小 * 2
- 从后往前遍历,对有元素的节点加锁转换。
- 转换完后把第一个节点变为 ForwardingNode,表示本节点扩容完成。
- 定义
- 直接使用transient volatile HashEntry<K,V>[] table保存数据,用table元素作为锁,实现对每一行数据加锁,减少冲突概率
- 将数组加链表 -> 数组 + 链表 + 红黑树
- 初始化
- table为空进行初始化
- 如果sizectl < 0 有线程在初始化,yield 让出CPU
- 初始化的时候让 sizectl = -1;
- size
- 通过volatile变量的basesize记录元素个数,添加,删除元素通过addCount方法更新 basecount
- 通过累加baseCount和CounterCell数组中的数量,即可得到元素的总个数
- 定义
- 每一个segment都是一个hashEntry<>[] table,每一个元素都是hashEntry的单向队列,减少并发冲突,不属于同一个segment节点并发操作
- segment 继承ReentrantLock可作为互斥锁使用
- put
- put的时候 通过hash找到segments 数组中的 segment,如果为 null,cas初始化,在加锁执行segment的put操作。
- size
- 连续计算元素个数,最多3次,如果连续两次不同,给每个segment加锁,在计算一次
- 结构
- 继承 HashMap
- accessOrder 控制元素的顺序
- false:按 put 元素的顺序存放
- true:按访问的顺序放
- entry,有前后两个指针,是数组和双向链表的结合体
- put方法
- 调用hashmap的put方法
- 如果找到对应的key,改变value,执行 afterNodeaccess
- 如果没有插入到最前面,插入后执行 afterNodeInsertion
- 调用hashmap的put方法
- afterNodeAccess 中如果 accessOrder = true,把当前元素插入到双向链表结尾
- get 方法,如果 accessOrder = true,get执行的时候会 执行 afterNodeaccess,将节点放在双向链表结尾
- ReentrantLock ReentrantReadWriteLock Semaphore CountDownLatch . 都有 Sync,都实现了 tryAcquire 方法,定义获取锁的方式,而其他内容,如加入等待线程链表就让 aqs实现,并调用了 aqs 的 acquire 和 relase方法
- state 线程通过这个变量的值来判断自己阻塞还是进入临界区,比如公平锁,如果state = 0,没线程抢占,看自己是不是等待队列中第一个线程,如果是,ExclusiveOwnerThread设为当前线程,如果不是加入队列中
- lockSuppoprt.pack/unpack 阻塞或唤醒线程, 实现一个线程对另一个线程精确控制,当ExclusiveOwnerThread运行完后,会LockSupport.unpark(s.thread);
- 无锁链表实现的所有阻塞线程,形成一个阻塞队列
- 提供了 acquire,acquireInterruptibly, 让子类实现 tryAcquire
- acquireQueued屏蔽中断,doAcquireInterruptibly响应中断,前者被中段继续死循环拿锁,后者返回并取消acquire
- 独占锁,只有一个线程拿到资源,释放锁的时候也只唤醒一个线程
- 共享锁,可以有多个线程拿到资源,释放的时候唤醒队列中的全部线程,都同时在去拿锁。
- semaphore acquire 只要 state > 0 就能拿到锁,否则加到阻塞队列中, relase时候,把 state + ,唤醒全部线程
- countDownLunch await 看 state是否为0,如果不为0、countDown还没减到0,把当前线程放在队列中,如果减到0到时候会唤醒队列中全部元素。
单向无锁链表实现的, 和 AQS 的 无锁阻塞队列比较类似,从尾入队,从头出队,用cas实现的无锁队列
private static class Node<E> {
volatile E item;
volatile Node<E> next;
- 结构
- ReentrantLock + condition(trip) + state 变量
- await
- 加锁, state--, 如果 state == 0,唤醒其他线程,并恢复 state,否则 trip.await 阻塞自己
- 方法
- 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) 固定间隔执行
- 内部实现
- 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