java 并发包-AbstractQueuedSynchronizer
来源:互联网 发布:node没有成员pnext 编辑:程序博客网 时间:2024/06/06 03:11
简介
AbstractQueuedSynchronizer 就是我们常说的AQS-同步器。常用的有ReentrantLock、ReadWriteLock、CountDownLatch,内部实现都依赖AQS类,可以说AQS是实现同比必备良药。
源码分析
AQS的主要参数 state字段 表示同步的状态,需要通过传入值与state进行比较是否一致。CountDownLatch就是通过state来控制达到阻塞目的,当为0的时候,释放。
//等待队列的头节点 private transient volatile Node head; //等待队列的尾节点 private transient volatile Node tail; //同步状态 private volatile int state;
AQS内部通过FIFO队列实现排队获取资源,通过Node的waitStatus来控制节点的状态,再由状态判断资源是否应该被该线程获取,
static final class Node { static final Node SHARED = new Node(); static final Node EXCLUSIVE = null; //取消状态 被打断或者超时,相当于该节点被废了 static final int CANCELLED = 1; //等待触发状态 当unpart时,只有被unpark才能释放 static final int SIGNAL = -1; //等待条件状态 等待某条件触发 static final int CONDITION = -2; //状态需要向后传播 static final int PROPAGATE = -3; //节点等待状态 volatile int waitStatus; //上一个节点 volatile Node prev; //下一个节点 volatile Node next; //节点对应的线程 volatile Thread thread; ..... }
获取锁资源
acquire方法是AQS提供对外占用资源的接口
1 首先调用子类重写tryAcquire方法,资源已被占用,则添加节点入队
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
入队主要通过自旋的方式、cas安全的将节点放至队列中
private Node enq(final Node node) { //自旋 for (;;) { Node t = tail; //尾节点为空,说明队列是空的 则赋值头部、尾部都为该节点 if (t == null) { if (compareAndSetHead(new Node())) tail = head; } else { //将上一个节点设置为当前尾节点 node.prev = t; //cas设置尾节点为改node 并且在设置原为节点的next指向当前node if (compareAndSetTail(t, node)) { t.next = node; return t; } } } } //添加等待节点 private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); Node pred = tail; if (pred != null) { //将当前节点的上一个节点设置为当前的尾节点 node.prev = pred; //如果这一步成功 则就直接返回 if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //上一步不成功,将调用enq方法,自旋直至设置成功 enq(node); return node; }
对当前节点进行自旋判断
1 当前节点的perv是否为头节点,如果是 这说明当前节点时排第一个的
2 再次尝试对比state是否满足当前值
1,2条件都满足,则返回
否则 往下走
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; //自旋 for (;;) { //前一个节点 final Node p = node.predecessor(); //每次循环都要去竞争,非公平 if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } //不满足 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
这一步主要是对等待状态进行判断
1 SIGNAL 时 直接返回true 则挂起等待被唤醒
2 >0 表示取消 则删除该节点
3 修改pred值为Node.SIGNAL 那么因为是自旋的原因,下一次有可能就是执行1步骤进行挂起操作
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) return true; if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
获取锁的过程就这样了。最后还需要说明的是,当被唤醒的时候,还要重新再次去竞争,然后循环上面的步骤,因为AQS获取锁是非公平的
释放锁
tryReleaseShared需要子类重写
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
1 如果head=tail 说明 队列空了,直接break
2 如果头节点的waitStatus为Node.SIGNAL 说明等待被唤醒
cas修改节点的waitStatus 成功 则 唤醒等待队列
3 继续自旋
private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; unparkSuccessor(h); } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; } if (h == head) break; } }
unparkSuccessor 才是正真唤醒释放的地方
1 由于唤醒完之后,该节点将被释放,所以要保证改节点下的下一个节点时有意义的 不能为null或者被取消
2 LockSupport.unpark(s.thread); 唤醒线程
private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread); }
总结:AQS 使用cas进行安全竞争资源,如果未竞争到 则根据条件通过LockSupport.park对线程进行挂起。
如果某个线程释放资源时,则同理 通过 LockSupport.unpark唤醒线程加入竞争的行列。
各线程如果加入了node队列中,则通过FIFO公平竞争,但是毕竟是高并发的情况下,还没有加入node队列中的N个线程和node队列的第一个线程就会相互竞争资源,也就是说 获取锁是不公平的。
- Java并发包--AbstractQueuedSynchronizer
- java 并发包-AbstractQueuedSynchronizer
- 读JAVA并发包之AbstractQueuedSynchronizer
- java并发之AbstractQueuedSynchronizer
- 【Java并发】详解 AbstractQueuedSynchronizer
- Java 并发包中AbstractQueuedSynchronizer 实现的同步器
- Java并发之AbstractQueuedSynchronizer分析
- java并发编程之AbstractQueuedSynchronizer
- Java JUC并发 AbstractQueuedSynchronizer学习
- Java并发编程(一)--AbstractQueuedSynchronizer
- Java并发学习(三)-AbstractQueuedSynchronizer
- [Java并发] AbstractQueuedSynchronizer实现(一)
- Java多线程并发器之AbstractQueuedSynchronizer分析
- 【Java并发】- AbstractQueuedSynchronizer详解(AQS)
- Java 并发 ---AbstractQueuedSynchronizer(同步器)-独占模式
- Java 并发 ---AbstractQueuedSynchronizer-共享模式与Condition
- Java --- AbstractQueuedSynchronizer
- Java-AbstractQueuedSynchronizer
- python学习_实现一个考拉咨猜想
- Scala学习(九)---文件和正则表达式
- Spring Boot简介
- Linux服务器 使用tc命令对网卡流量上下行限速
- leetcode—Median of Two Sorted Arrays
- java 并发包-AbstractQueuedSynchronizer
- LeetCode 20. Valid Parentheses
- Java生成无限制带参小程序码
- centos7上安装gitlab,配置和汉化。
- JPA概述
- Eclipse报错:java.lang.ClassNotFoundException: ContextLoaderListener
- PyInstaller 的作用
- 手机端web研发(即在手机上用浏览器进行访问的研发),研发环境搭建,涉及的几个技术,node.js,npm,ionic,vscode
- Pyhon多线程(1)