AbstractQueuedSynchronizer
lock相关操作
-
public final void acquire(int arg)
- 这个函数的作用是获取同一时间段只能被一个线程获取的量,这个量就是抽象化的锁的概念
- 首先会执行tryAcquire方法尝试获取“锁”,如果获取到了就没事了,如果没有获取到会执行下面的操作
- 如果没有获取到“锁”会执行addWaiter方法,会给当前线程创建一个节点,然后将其加入到等待队列中
- 然后执行acquireQueued方法,继续尝试获取锁
- 如果一直获取不到,会执行selfInterrupt方法,该方法实现只有一句Thread.currentThread().interrupt();
-
protected boolean tryAcquire(int arg)
-
这个方法需要子类自行实现,总之含义就是尝试获取“锁”,具体实现在实现类中有写
-
private Node addWaiter(Node mode)
-
首先会调用Node(Thread thread, Node mode)构造,保存当前线程信息以及模式(共享或独占模式)
-
获取队列尾节点
-
将待添加的节点的pred置为队尾
-
执行compareAndSetTail方法将尾指针指向待添加节点
-
如果成功的话再将旧的尾节点的next指向待添加节点
-
添加成功返回节点
-
如果cas失败,需要执行enq方法使用更为严谨的方式来入队
-
compareAndSetTail
- 该方法中调用了unsafe.compareAndSwapObject方法,就是比较并交换算法
-
enq
- 死循环一直尝试将待添加节点添加到队尾
- 标准的CAS无锁算法。。。死循环
-
final boolean acquireQueued(final Node node, int arg)
-
在执行完addWaiter后,线程被加入到阻塞队列中,但是当前线程还没有真正的阻塞,addWaiter返回了待阻塞线程的节点,就是这个参数node
-
由于进入阻塞状态的话效率比较低,所以AQS会尽量避免尝试获取线程独占变量的线程进入到阻塞状态,所以这个新节点刚加入队列中时,会使用一个死循环来判断这个节点是否已经在队首,如果在队首就可以尝试获取这个独占变量了,但是如果不在队首或者再次尝试获取独占变量时失败了,会调用shouldParkAfterFailedAcquire方法判断这个线程是否应该进入阻塞状态,如果当前节点的前驱节点已经进入阻塞状态了,那这个新节点也就没有必要一直判断了,空空浪费CPU资源,所以会调用parkAndCheckInterrupt方法来进入阻塞状态
-
方法具体实现
-
final boolean acquireQueued(final Node node, int arg) { boolean failed = true;//该变量用于判断获取锁是否失败 try { boolean interrupted = false;//该变量用于对调用者声明是否需要执行selfInterrupt中断线程 //一个死循环,直到已经获取锁或者需要中断了 for (;;) { final Node p = node.predecessor();//取得当前节点的前驱节点 //如果前驱节点就是头节点,就说明下一个可以获取锁的就是当前节点 //然后调用tryAcquire方法继续尝试获取锁 if (p == head && tryAcquire(arg)) { //如果尝试获取到了锁,会把当前节点设置为头节点 setHead(node); //将前驱节点置空,避免强引用导致无法GC p.next = null; // help GC //失败标志位设置为false failed = false; //未修改过interrupted变量,所以返回的是false,即不需要进行中断 return interrupted; } //执行到这里的话说明上面的判断未成立,即当前节点还不能获取锁 //会执行shouldParkAfterFailedAcquire方法判断当前线程需不需要进入阻塞状态 //如果shouldParkAfterFailedAcquire为真表示需要进入阻塞状态,会调用parkAndCheckInterrupt方法进入阻塞状态 //如果不需要进行阻塞操作,则继续进行for循环 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) //进入阻塞状态后通知调用者中断该线程 interrupted = true; } } finally { //中途抛出任何异常,都表示获取锁失败,此时需要调用cancelAcquire来取消获取锁 if (failed) cancelAcquire(node); } } 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 { //否则通过CAS将前驱结点的状态修改为等待独占变量释放 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } //上述操作执行后返回不需要阻塞 return false; } private final boolean parkAndCheckInterrupt() { //该方法会将当前线程进入阻塞状态,并返回当前线程是否已经中断 LockSupport.park(this);//该方法将AQS对象传入,阻塞当前线程 return Thread.interrupted(); } public static void park(Object blocker) { Thread t = Thread.currentThread();//获取当前线程 setBlocker(t, blocker);//设置阻塞对象,可以记录线程是被谁阻塞的 UNSAFE.park(false, 0L);//该方法会让当前线程不再被CPU调度 setBlocker(t, null); }
-
unlock相关操作
- 调用relase方法
- 先调用
tryRelease
来释放独占性变量。如果成功,那么就看一下是否有等待锁的阻塞线程,如果有,就调用unparkSuccessor
来唤醒他们 tryRelease
中的逻辑也体现了可重入锁的概念,只有等到state
的值为0时,才代表锁真正被释放了。所以独占性变量state
的值就代表锁的有无。当state=0
时,表示锁未被占有,否在表示当前锁已经被占有。- 调用了
unpark
方法后,进行lock
操作被阻塞的线程就恢复到运行状态,就会再次执行acquireQueued
中的无限for循环中的操作,再次尝试获取锁。