1、ReentrantLock中其实是有一个AQS的子类实例的成员变量sync;
2、实际是调用的Sync中的lock;Sync是AQS的子类;Sync有两个子类,公平与非公平;默认为非公平;如下是非公平加锁分析;
public ReentrantLock() {sync = new NonfairSync();}
3、cas进行加锁:0->1,成功即加锁成功,并进行业务逻辑处理,然后解锁;
compareAndSetState(0, 1)
4、cas加锁:0->1 会有两种情况导致失败:
- 锁被其它线程占用;
- 已经被当前线程(自己)占用;
final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}
5、cas失败,则acquire(1);
public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
如上可以避免一些情况下导致刚刚加入队列的线程无法被唤醒的情况。
6、进行tryAcquire(1);其中会再一次进行cas设置,进行锁重入逻辑判断并处理;
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 如果此时锁被释放了,再进行一次cas设置if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}// 进行锁重入判断及加锁次数++操作else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
如上代码,我曾还怀疑是否存在 setState(nextc); 线程安全的问题?
心想着在else if (current == getExclusiveOwnerThread()) 判断成立之后,锁被释放,并且被其它线程重新获取锁成功。然后再进行 int nextc = c + acquires; setState(nextc); 就会存在线程不安全问题。
然后仔细琢磨,current == getExclusiveOwnerThread() 既然成立,说明持有当前锁的线程就是正在执行当前方法逻辑的线程,那么既然当前线程正在执行tryAcquire逻辑,又怎么可能在这个时候去释放锁,所以这里是线程安全的。
7、如果 !tryAcquire(arg) 再次cas没有成功并且不是进行多次重入加锁,那么就会进行将当前线程构建成Node并加入队列的操作;
8、addWaiter(Node.EXCLUSIVE),创建一个等待Node,并且这个Node是一个互斥独占的类型;
static final Node SHARED = new Node();static final Node EXCLUSIVE = null;addWaiter(Node.EXCLUSIVE)Node node = new Node(Thread.currentThread(), mode);Node(Thread thread, Node mode) {// 这样就很方便的知道了当前队列是共享型阻塞队列,还是独占型阻塞队列this.nextWaiter = mode;this.thread = thread;}
private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);Node pred = tail;if (pred != null) {// 表示队列之前已经被初始化过node.prev = pred;// 如果入队cas没有成功则会进行enq(node)if (compareAndSetTail(pred, node)) {// 这里node入队列尾部,同样使用cas做了并发安全控制pred.next = node;return node;}}// 将节点插入队列,如有必要进行初始化enq(node);return node;}
9、将节点插入队列,如有必要进行初始化。enq(node); 如下操作会一直自旋直到把node插入队列为止!
private Node enq(final Node node) {for (;;) {// 这里就必须要把node搞进队列为止了,如果一次不行则两次......Node t = tail; // 所以tail必须为volatile修饰if (t == null) { // Must initialize// 队列初始化if (compareAndSetHead(new Node()))// 虚拟一个Node,设置为头(不包含线程)// 如果不成功则下次for循环tail = head;} else {// 队列已经存在nodenode.prev = t;if (compareAndSetTail(t, node)) { // 将新增的node设置为尾t.next = node;return t;}}}}
10、acquireQueued(node,1);这个方法中包含三种情况的逻辑;
- 当前线程的Node的pre就是head,并且再次尝试cas:0->1成功,那么就需要将当前线程的Node设置为head,并且将原先的head进行出队列;(如果当前线程想要去竞争锁,那么前提条件是当前线程的Node必须是head)
- 当前线程的Node的pre不是head或者cas:0->1失败,那么就需要将当前线程进行park让其阻塞,等待被释放锁的线程唤醒;
- Node对应的线程被唤醒后,重新执行for循环判断,cas获取锁;
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {// 获取当前线程Node的前驱节点final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {// 当前线程获取锁成功,及当前参数Node中的线程就是当前线程// 并且当前Node是此时队列中的第二个// 那么代表第一个Node已经使用完锁,需要将其进行从队列中移除// 当前线程的Node设置为队列的头setHead(node);p.next = null; // help GCfailed = false;return interrupted;}// 进行线程阻塞操作if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()) // 阻塞当前线程,被唤醒后判断当前线程释放被中断interrupted = true; // 阻塞被唤醒后并且当前线程已经被中断}} finally {if (failed)cancelAcquire(node);}}private void setHead(Node node) {head = node;node.thread = null;node.prev = null;}
11、shouldParkAfterFailedAcquire(Node pred, Node node),当前线程Node的前驱节点的状态是SIGNAL(前驱节点有义务唤醒它的下一个)时,才会将当前线程进行park操作;
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL)/** This node has already set status asking a release* to signal it, so it can safely park.*/// 当前驱节点被赋予责任后(需要唤醒后续节点)return true;else {/** waitStatus must be 0 or PROPAGATE. Indicate that we* need a signal, but don't park yet. Caller will need to* retry to make sure it cannot acquire before parking.*/// 这里会将前驱阶段的状态设置为:需要唤醒后续节点compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}
12、parkAndCheckInterrupt(),进行当前线程park操作;
private final boolean parkAndCheckInterrupt() {// 这里会进行当前线程阻塞,直到被唤醒LockSupport.park(this);// 检查被唤醒的线程释放已经被中断return Thread.interrupted();}
13、会有两种情况线程会被唤醒继续执行 return Thread.interrupted(); 之后的逻辑
- 被park阻塞后的线程被唤醒,并且目标线程被中断,那么上面方法会返回true;
- 被park阻塞后的线程被唤醒,目标线程未被中断;
14、被唤醒的线程再次cas尝试获取锁,如果获取锁失败会继续进行park,循环往复,直到获取锁成功。
好文推荐:
AQS源码分析(四)[lock流程图总结与unlock源码分析]_哔哩哔哩_bilibili