图解AQS原理之ReentrantLock详解-非公平锁 (7)

发现他调用的Sync类中的acquireInterruptibly方法,但其实这个方法是AQS中的方法,源码如下所示:

public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) //判断线程是否被中断 throw new InterruptedException(); //中断则抛出异常 if (!tryAcquire(arg)) //尝试获取锁 doAcquireInterruptibly(arg); //进行添加队列,并且修改前置节点状态,且响应中断抛出异常 }

通过上面的源码,它也调用了子类实现的tryAcquire方法,这个方法和我们上文提到的tryAcquire是一样,ReentrantLock下的NonfairSync下的tryAcquire方法,这里这个方法就不多说了详细请看上文内容,这里主要讲一下doAcquireInterruptibly这个方法:

private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); //将节点添加到队列尾部 boolean failed = true; //失败匹配机制 try { for (;;) { final Node p = node.predecessor(); //获取前节点 if (p == head && tryAcquire(arg)) { //如果前节点为头节点并且获得了锁 setHead(node); //设置当前节点为头节点 p.next = null; // help GC //头节点的下一个节点设置为null failed = false; //匹配失败变为false return; } if (shouldParkAfterFailedAcquire(p, node) && //将前节点设置为-1,如果前节点为取消节点则往前一直寻找直到修改为-1为止。 parkAndCheckInterrupt()) //挂起线程返回是否中断 throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }

其实这个方法和acquireQueued区别在于以下几点:

acquireQueued是在方法内部添加节点到队列尾部,而doAcquireInterruptibly是在方法内部进行添加节点到尾部,这个区别点并不是很重要

重点是acquireQueued响应中断,但是他不会抛出异常,而后者会抛出异常throw new InterruptedException()

分析到这里我们来用前面的例子来进行模拟一下中中断的操作,详细代码如下所示:

public class ReentrantLockDemo { public static void main(String[] args) throws Exception { AddDemo runnalbeDemo = new AddDemo(); Thread thread = new Thread(runnalbeDemo::add); thread.start(); Thread.sleep(500); Thread thread1 = new Thread(runnalbeDemo::add); thread1.start(); Thread.sleep(500); Thread thread2 = new Thread(runnalbeDemo::add); thread2.start(); Thread.sleep(500); Thread thread3 = new Thread(runnalbeDemo::add); thread3.start(); Thread.sleep(10000); thread1.interrupt(); System.out.println(runnalbeDemo.getCount()); } private static class AddDemo { private final AtomicInteger count = new AtomicInteger(); private final ReentrantLock reentrantLock = new ReentrantLock(); private final Condition condition = reentrantLock.newCondition(); private void add() { try { reentrantLock.lockInterruptibly(); count.getAndIncrement(); } catch (Exception ex) { System.out.println("线程被中断了"); } finally { // reentrantLock.unlock(); } } int getCount() { return count.get(); } } }

上面的例子其实和前面提到的例子没有什么太大的差别主要的差别是将lock替换为lockInterruptibly,其次就是在三个线程后面讲线程1进行中断操作,这里入队的操作不在多说,因为操作内容和上面大致相同,下面是四个个线程操作完成的状态信息:

图解AQS原理之ReentrantLock详解-非公平锁

如果线程等待的过程中抛出异常,则当前线程进入到finally中的时候failed为true,因为修改该字段只有获取到锁的时候才会修改为false,进来之后它会运行cancelAcquire来进行取消当前节点,下面我们先来分析下源码内容:

private void cancelAcquire(Node node) { // 如果节点为空直接返回,节点不存在直接返回 if (node == null) return; // 设置节点所在的线程为空,清除线程操作 node.thread = null; // 获取当前节点的前节点 Node pred = node.prev; // 如果前节点是取消节点则跳过前节点,一直寻找一个不是取消节点为止 while (pred.waitStatus > 0) node.prev = pred = pred.prev; // 获取头节点下一个节点 Node predNext = pred.next; // 这里直接设置为取消节点状态,没有使用CAS原因是因为直接设置只有其他线程可以跳过取消的节点 node.waitStatus = Node.CANCELLED; // 如果当前节点为尾节点,并且设置尾节点为找到的合适的前节点时,修改前节点的下一个节点为null if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); } else { // 如果不是尾节点,则说明是中间节点,则需要通知后续节点,嘿,伙计你被唤醒了。 int ws; if (pred != head && //前节点不是头结点 ((ws = pred.waitStatus) == Node.SIGNAL || // 前节点的状态为SIGNAL (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) //或者前节点状态小于0而且修改前节点状态为SIGNAL成功 && pred.thread != null) { //前节点线程不为空 Node next = node.next; if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); } else { //唤醒下一个不是取消的节点 unparkSuccessor(node); } node.next = node; // help GC } }

首先找到当前节点的前节点,如果前节点为取消节点则一直往前寻找一个节点。

取消的是尾节点,则直接将前节点的下一个节点设置为null

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyffxz.html