AQS与ReentrantLock详解

本章内容是AQS原理,但仅谈AQS会显得很乱,因此以它的其中一个实现ReentrantLock进行切入。

一、ReentrantLock功能详解

ReentrantLock是一个可重入的互斥锁,它具有与synchronized所代表的隐式监视器锁相同的一些基本行为和语义,但它的功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。

1.1 ReentrantLock是可重入且互斥的

  1 public class Test3 {
  2    static class RunnableTest implements Runnable{
  3         private ReentrantLock lock = new ReentrantLock();
  4 
  5         @Override
  6         public void run() {
  7             lock.lock();
  8             System.out.println(Thread.currentThread().getName()+" get the lock.");
  9             try {
 10                 runTest();
 11             }finally {
 12                 lock.unlock();
 13                 System.out.println(Thread.currentThread().getName()+" release the lock.");
 14             }
 15         }
 16 
 17         private void runTest(){
 18             lock.lock();
 19             try {
 20                 TimeUnit.SECONDS.sleep(3);
 21             } catch (InterruptedException e) {
 22                 e.printStackTrace();
 23             }finally {
 24 			    System.out.println(Thread.currentThread().getName()+" has finished the task.");
 25                 lock.unlock();
 26             }
 27         }
 28     }
 29 
 30 public static void main(String[] args) {
 31         RunnableTest runnableTest = new RunnableTest();
 32         new Thread(runnableTest).start();
 33         new Thread(runnableTest).start();
 34         new Thread(runnableTest).start();
 35     }
 36 }
 37 //Thread-0 get the lock.
 38   Thread-0 has finished the task.
 39   Thread-0 release the lock.
Test3

该例每次只有一个线程获得锁,是独占的;每次线程执行过程中都会获取两次锁,是可重入的。这与synchronized功能相同,不同的是synchronized的加锁解锁过程隐式的,ReentrantLock需要手动加锁和解锁,并且解锁操作要尽量放在finally块中,要保证加锁次数和解锁次数相同,否则可能会造成其他线程无法获取到锁。大规模并发下,两者性能相差不大,并且ReentrantLock的功能比synchronized更丰富:

1.2 ReentrantLock实现公平锁

  1 public class Test3 {
  2     public static void main(String[] args) {
  3         RunnableTest test = new RunnableTest();
  4         new Thread(test, "Thread_1").start();
  5         new Thread(test, "Thread_2").start();
  6     }
  7 
  8     static class RunnableTest implements Runnable{
  9         private ReentrantLock lock = new ReentrantLock(true);
 10         @Override
 11         public void run() {
 12            excute();
 13         }
 14 
 15         private void excute(){
 16             for (int i = 0;i < 2;i++){
 17                 lock.lock();
 18                 System.out.println(Thread.currentThread().getName()+" get the lock.");
 19                 try {
 20                     TimeUnit.SECONDS.sleep(3);
 21                 } catch (InterruptedException e) {
 22                     e.printStackTrace();
 23                 }finally {
 24                     System.out.println(Thread.currentThread().getName()+" release the lock.");
 25                     lock.unlock();
 26                 }
 27             }
 28         }
 29     }
 30 }
 31 公平执行结果:
 32 Thread_1 get the lock.
 33 Thread_1 release the lock.
 34 Thread_2 get the lock.
 35 Thread_2 release the lock.
 36 Thread_1 get the lock.
 37 Thread_1 release the lock.
 38 Thread_2 get the lock.
 39 Thread_2 release the lock.
 40 非公平执行结果:
 41 Thread_1 get the lock.
 42 Thread_1 release the lock.
 43 Thread_1 get the lock.
 44 Thread_1 release the lock.
 45 Thread_2 get the lock.
 46 Thread_2 release the lock.
 47 Thread_2 get the lock.
 48 Thread_2 release the lock.
Test3

每个线程执行任务时会获取两次锁,第一次释放锁后立刻再去获取锁。公平锁下,这些锁倾向于将访问权授予等待时间最长的线程,与非公平锁相比,使用公平锁的程序在许多线程访问时表现为很低的总体吞吐量,但是在获得锁和保证锁分配的均衡性时差异较小。不过,公平锁不能保证线程调度的公平性。另外,未定时的 tryLock 方法并没有使用公平设置,只要该锁未被别的线程持有就立即获取锁。

1.3 ReentrantLock可响应中断与限时等待

synchronized与Lock在默认情况下是不会响应中断(interrupt)操作,阻塞在锁上的线程除非获得锁否则将一直等待下去。ReentrantLock提供了可响应中断的方法lockInterruptibly( )。

  1 public class Test4{
  2     public static void main(String[] args) throws InterruptedException {
  3         ReentrantLock lock1 = new ReentrantLock();
  4         ReentrantLock lock2 = new ReentrantLock();
  5         Thread thread1 = new Thread(new RunnableTest(lock1, lock2), "Thread_1");
  6         Thread thread2 = new Thread(new RunnableTest(lock2, lock1), "Thread_2");
  7         thread1.start();
  8         thread2.start();
  9 
 10         TimeUnit.SECONDS.sleep(2);
 11 
 12         thread1.interrupt();
 13     }
 14 
 15     static class RunnableTest implements Runnable{
 16         private ReentrantLock lock1;
 17         private ReentrantLock lock2;
 18 
 19         public RunnableTest(ReentrantLock lock1,ReentrantLock lock2) {
 20             this.lock1 = lock1;
 21             this.lock2 = lock2;
 22         }
 23 
 24         @Override
 25         public void run() {
 26             try {
 27                 lock1.lockInterruptibly();
 28                 TimeUnit.SECONDS.sleep(1);
 29                 System.out.println(Thread.currentThread().getName()+" get another lock");
 30                 lock2.lockInterruptibly();
 31             } catch (InterruptedException e) {
 32                 e.printStackTrace();
 33             }finally {
 34                 lock1.unlock();
 35                 lock2.unlock();
 36             }
 37         }
 38     }
 39 }
 40 //Thread_2 get another lock
 41   Thread_1 get another lock
 42   java.lang.InterruptedException
Test4

该方法,如果锁被另一个线程保持,那么在其他某个线程中断当前线程之前或者当前线程获取到锁之前,该线程将一直处于休眠状态。该死锁案例中,线程1在获取lock2时陷入休眠,直到被中断,获取锁失败。

除此之外,ReentrantLock还提供获取锁时的限时等待方法tryLock(long timeout, TimeUnit unit)。超时后返回false,该方法遵守公平设置,而tryLock( )方法不遵守公平设置。

  1 public class Test5{
  2     public static void main(String[] args) throws InterruptedException {
  3         RunnableTest test = new RunnableTest();
  4         new Thread(test, "Thread_1").start();
  5         new Thread(test, "Thread_2").start();
  6     }
  7 
  8     static class RunnableTest implements Runnable{
  9         private ReentrantLock lock = new ReentrantLock();
 10 
 11         @Override
 12         public void run() {
 13             try {
 14                 if (lock.tryLock(3, TimeUnit.SECONDS)) {
 15                     System.out.println(Thread.currentThread().getName() + " get the lock");
 16                     TimeUnit.SECONDS.sleep(5);
 17                 }
 18             } catch (InterruptedException e) {
 19                 e.printStackTrace();
 20             }finally {
 21                 lock.unlock();
 22             }
 23         }
 24     }
 25 }
 26 //hread_1 get the lock
 27   Exception in thread "Thread_2"
Test5

    1.4 ReentrantLock中的通信

    synchronized通过wait( )和notify( )实现线程通信,ReentrantLock替代了 synchronized 方法和语句的使用,也通过Condition替代了Object 监视器方法的使用。Condition条件使用方法与wait( )方法相同,使用前需要先获取到锁,然后await( )方法将锁释放掉,该线程在Condition条件下等待,知道其他线程通过signalAll( )唤醒该线程。

      1 public class ProduceAndConsume {
      2     private ReentrantLock lock = new ReentrantLock();
      3     private Condition condition = lock.newCondition();
      4     private int i;
      5     private final static int MAXVALUE = 2;
      6 
      7     public void produce() {
      8         lock.lock();
      9         try {
     10             while (MAXVALUE <= i)
     11                 condition.await();
     12             System.out.println(Thread.currentThread().getName()+" 生产 "+(++i));
     13             condition.signalAll();
     14     } catch (InterruptedException e) {
     15             e.printStackTrace();
     16         }finally {
     17             lock.unlock();
     18         }
     19     }
     20 
     21     public void consume(){
     22         lock.lock();
     23         try {
     24             while (MAXVALUE > i)
     25                 condition.await();
     26             System.out.println(Thread.currentThread().getName()+" 消费 "+(--i));
     27             condition.signalAll();
     28     } catch (InterruptedException e) {
     29             e.printStackTrace();
     30         } finally {
     31             lock.unlock();
     32         }
     33     }
     34 
     35     public static void main(String[] args) {
     36         ProduceAndConsume produceAndConsume = new ProduceAndConsume();
     37         new Thread(new Runnable() {
     38             @Override
     39             public void run() {
     40                 while (true) {
     41                         produceAndConsume.produce();
     42                 }
     43             }
     44         }, "Thread1").start();
     45 
     46         new Thread(new Runnable() {
     47             @Override
     48             public void run() {
     49                 while (true) {
     50                     produceAndConsume.consume();
     51                 }
     52             }
     53         }, "Thread2").start();
     54     }
     55 }
    ProduceAndConsume1
      1 public class ProduceAndConsume2 {
      2     final Lock lock = new ReentrantLock();
      3     final Condition notFull = lock.newCondition();
      4     final Condition notEmpty = lock.newCondition();
      5 
      6     final Object[] items = new Object[10];
      7     int putptr, takeptr, count;
      8 
      9     public void put(Object x) throws InterruptedException {
     10         lock.lock();
     11         try {
     12             while (count == items.length)
     13                 notFull.await();
     14             items[putptr] = x;
     15             if (++putptr == items.length)
     16                 putptr = 0;
     17             ++count;
     18             notEmpty.signal();
     19         }finally {
     20             lock.unlock();
     21         }
     22     }
     23 
     24     public Object take() throws InterruptedException {
     25         lock.lock();
     26         try {
     27             while (count == 0)
     28                 notEmpty.await();
     29             Object x = items[takeptr];
     30             if (++takeptr == items.length)
     31                 takeptr = 0;
     32             --count;
     33             notFull.signal();
     34             return x;
     35         } finally {
     36             lock.unlock();
     37         }
     38     }
     39 }
    ProduceAndConsume2

    二、AQS简介

    AQS(AbstractQueuedSynchronizer)以模板方法模式在内部定义了获取和释放同步状态的模板方法,并留下钩子函数供子类继承时进行扩展,由子类决定在获取和释放同步状态时的细节,从而实现满足自身功能特性的需求。除此之外,AQS通过内部的同步队列管理获取同步状态失败的线程,向实现者屏蔽了线程阻塞和唤醒的细节。

    总的来说AQS为实现同步器提供了一个框架,具体实现由子类自己扩展,这个同步器依赖于一个先进先出的队列、阻塞的锁和表示状态的单个原子int值。接下来根据源码具体讨论。

    2.1 Node类

    Node是阻塞队列的节点类,注释中作者用很多文字讲解CLH,因为该AQS是CLH的一个变种,但没必要去纠结CLH。

      1 static final class Node {
      2 	//共享模式下的等待节点
      3 	static final Node SHARED = new Node();
      4 	//独占模式下的等待节点
      5 	static final Node EXCLUSIVE = null;
      6 
      7 	//当前等待节点的线程已被取消
      8 	static final int CANCELLED =  1;
      9 	//当前节点需要唤醒其后续节点的线程
     10 	static final int SIGNAL    = -1;
     11 	//当前等待节点的线程在Condition上等待
     12 	static final int CONDITION = -2;
     13 	//共享模式下,表示下次同步状态会无条件传播下去
     14 	static final int PROPAGATE = -3;
     15 
     16     //共5个值
     17 	volatile int waitStatus;
     18 
     19 	//前置节点
     20 	volatile Node prev;
     21 
     22 	//后继节点
     23 	volatile Node next;
     24 
     25     //阻塞的线程,用完后置为null回收
     26 	volatile Thread thread;
     27 
     28 	/**
     29 	 *用于条件队列
     30 	 */
     31 	Node nextWaiter;
     32 
     33     //用于创建head或为nextWaiter设置共享标记
     34 	Node() {
     35 	}
     36 
     37     //addWaiter使用
     38 	Node(Thread thread, Node mode) {
     39 		this.nextWaiter = mode;
     40 		this.thread = thread;
     41 	}
     42 
     43 	//Condition使用
     44 	Node(Thread thread, int waitStatus) {
     45 		this.waitStatus = waitStatus;
     46 		this.thread = thread;
     47 	}
     48 }

    Node类中包含几个int值,通过让赋予waitStatus不同的值,来表示当前节点状态(也反映出线程状态):

    • SIGNAL:此节点的后继节点(或将很快)被阻塞(通过park),因此当前节点在释放锁的时候或者被取消时收必须unpark它的后继节点。为了避免竞争,获取方法必须一开始就表明它们需要signal,然后重新原子获取锁,如果失败则阻塞。
    • CANCELLED:当前节点由于被中断或者超时而被取消。那么这个节点的status一直保持着取消的状态,并且再也不能参与锁的竞争了,直到某时刻被回收。
    • CONDITION:这个节点现在在条件队列中。这个状态将不会在同步队列中的节点,直到这个节点的状态由condition变成0,那么将会把这个节点移到同步队列中。
    • PROPAGATE:在共享模式下释放锁的时候,应该把同步状态传播给其他节点。即使已经做了其他操作,但是为了保证传播的继续,在共享模式下释放同步状态的时候,应该给节点(只有头结点)的状态设置为progate。
    • 0:不是上面四种状况的情景

    上面值的排列可以简化使用,只要值为负,意味着不需要唤醒,大多数时候仅仅只用判断值的符号即可1。对于普通同步节点初始化值为0,对于条件节点初始化为CONDITION。修改是使用CAS进行

      1 public abstract class AbstractQueuedSynchronizer
      2     extends AbstractOwnableSynchronizer
      3     implements java.io.Serializable {
      4 
      5     private static final long serialVersionUID = 7373984972572414691L;
      6 
      7     protected AbstractQueuedSynchronizer() { }
      8 
      9     /**
     10 	 *队列的头,第一次使用时才加载。除了初始化,只有通过setHead()方法修改。只要head
     11 	 *存在,那么其waitStatus保证不会被取消
     12 	 */
     13     private transient volatile Node head;
     14 
     15     /**
     16      *队尾,第一次使用时才加载。仅在enq()方法中使用,以添加新的等待节点。
     17      */
     18     private transient volatile Node tail;
     19 
     20     /**
     21      * 同步状态,其在不同的子类实现中的意义不同,在ReentrantLock中表示锁占有情况
     22      */
     23     private volatile int state;
     24 
     25 	/**
     26 	 *为提高响应速度,设置的自旋超时阈值(在park之前先进行此时间的自旋)
     27 	 */
     28     static final long spinForTimeoutThreshold = 1000L;
     29 
     30 	 /**
     31      * 独占模式下持有同步状态(锁)的线程,这是AQS父类中的变量
     32      */
     33 	private transient Thread exclusiveOwnerThread;
     34 }

    AQS用链表维护的一个先进先出队列。这里也就是文档中说到的队列、int值和状态。目前为止这个队列还只能将线程封装进一个复杂的节点保存下来,它是如何阻塞的?用什么实现阻塞的?int值又是如何控制节点状态的?我们以ReentrantLock实现为例,跟踪其非公平模式,后续再比较公平模式。

    2.2 lock方法

    2.2.1 acquire方法

      1 public class ReentrantLock implements Lock, java.io.Serializable {
      2 
      3     private final Sync sync;
      4 
      5 	public ReentrantLock() {
      6 			sync = new NonfairSync();
      7 		}
      8 
      9     public ReentrantLock(boolean fair) {
     10         sync = fair ? new FairSync() : new NonfairSync();
     11     }
     12 }

    ReentrantLock中默认采用非公平锁。

      1 final void lock() {
      2      //尝试获取设置state,设置成功则将当前线程设置为持有锁线程
      3 	if (compareAndSetState(0, 1))
      4           setExclusiveOwnerThread(Thread.currentThread());
      5 	else
      6           acquire(1);
      7 }
      1 public final void acquire(int arg) {
      2 	if (!tryAcquire(arg) &&
      3 		acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
      4 	    selfInterrupt();
      5 }

    lock( )方法首先尝试设置state,state在ReentrantLock表示锁被重入的次数,因此只有在锁未被任何线程(即使是当前已经持有的锁的线程)持有时才能设置成功,否则执行acquire( )方法。

    acquire( )方法未被子类重写,其中有四个重要方法,tryAcquire( )方法获取锁失败返回false,才会执行后面的操作。

    2.2.2 tryAcquire方法

      1 protected final boolean tryAcquire(int acquires) {
      2 	return nonfairTryAcquire(acquires);
      3 }
      4 
      5 final boolean nonfairTryAcquire(int acquires) {
      6 
      7 	//获取当前执行此方法的线程
      8 	final Thread current = Thread.currentThread();
      9 	int c = getState();
     10 
     11 	//锁未被任何线程持有,尝试获取
     12 	if (c == 0) {
     13 	    if (compareAndSetState(0, acquires)) {
     14 		 setExclusiveOwnerThread(current);
     15 		 return true;
     16 	     }
     17 	}
     18 	//如果当前线程正持有锁,说明是重入操作,state+1获取成功
     19 	else if (current == getExclusiveOwnerThread()) {
     20 	       int nextc = c + acquires;
     21 	       if (nextc < 0) // overflow
     22 		    throw new Error("Maximum lock count exceeded");
     23 
     24 		//更新state值,因为当前线程已经持有锁,所以不用CAS方式进行更新
     25 	       setState(nextc);
     26 	       return true;
     27 	}
     28 
     29 	//获取锁失败
     30 	return false;
     31 }

    tryAcquirei( )是AQS定义的一个钩子方法,在公平模式和非公平模式下不同,非公平模式下在NonfairSync类中被重写,调用的是Sync中的nonfairTryAcquire( )方法。

    nonfairTryAcquire( )包含了线程获取锁时的所有情况:

    • state==0,锁未被任何线程获取
    • state>0&&current==ExclusiveOwnerThread,当前线程已经持有锁,再次获取锁
    • state>0&&current !=ExclusiveOwnerThread,锁已被其他线程持有

    2.2.3 addWaiter方法

      1 private Node addWaiter(Node mode) {
      2 	Node node = new Node(Thread.currentThread(), mode);
      3 	// Try the fast path of enq; backup to full enq on failure
      4 	Node pred = tail;
      5 
      6 	//pred==null说明tail和head还未初始化
      7 	if (pred != null) {
      8 
      9 	    //将node的前置节点指向pred
     10 	    node.prev = pred;
     11 
     12 	    //pred在此刻仍然是tail,即未被其他线程更改,则将node更新为新的tail节点
     13         //这是一次尝试更新
     14 	    if (compareAndSetTail(pred, node)) {
     15 		 pred.next = node;
     16 	       	 return node;
     17 	    }
     18 	}
     19 	enq(node);
     20 	return node;
     21 }
      1 private Node enq(final Node node) {
      2 	for (;;) {
      3 	     //重新获取tail节点
      4 	     Node t = tail;
      5 
      6 	     //尾节点为null,需要对head和tail初始化
      7 	     if (t == null) { // Must initialize
      8 
      9 		 //因为同时有多个线程进行操作,所以用CAS方式进行
     10 		 //这里head=new Node(),即waitStatus=0
     11 		 if (compareAndSetHead(new Node()))
     12 		       tail = head;
     13 	      } else {
     14 		       node.prev = t;
     15 		       if (compareAndSetTail(t, node)) {
     16 			     t.next = node;
     17 			     return t;
     18 			}
     19 	      }
     20 	 }
     21 }

    addWaiter( )方法进行了入队操作,其中封装线程时使用Node(Thread thread, Node mode)构造方法,传入了Node的mode,ReentrantLock中mode传入是EXCLUSIVE模式。(入队时的操作,都是先将当前节点的前置节点指向尾结点,再进行判断,个人觉得这个顺序安排的很好)。另一个有趣的是addWaiter( )和enq( )中出现了两次更新tail节点的代码,addWaiter( )中的实际冗余了,不过这样的冗余却一定程度提高了效率。

    2.2.4 acquireQueued方法

      1 final boolean acquireQueued(final Node node, int arg) {
      2 	boolean failed = true;
      3 	try {
      4 	      boolean interrupted = false;
      5 	      for (;;) {
      6 	            final Node p = node.predecessor();
      7 
      8 		    //如果node前置节点是head,则node中线程尝试获取锁
      9 		    if (p == head && tryAcquire(arg)) {
     10 
     11 	            //获取锁后首先设置node为head节点
     12 	            //回收p,即原来的head节点
     13 			 setHead(node);
     14 			 p.next = null; // help GC
     15 			 failed = false;
     16 			 return interrupted;
     17 		     }
     18 		     if (shouldParkAfterFailedAcquire(p, node) &&
     19 			            parkAndCheckInterrupt())
     20 			  interrupted = true;
     21 		}
     22 	} finally {
     23 	      if (failed)
     24 	           cancelAcquire(node);
     25 	}
     26 }

    acquireQueued( )方法主要有两个部分,一是对刚入队的节点,再次尝试获取锁;二是,若该节点暂时获取不到锁,则进行阻塞操作。

      1 private void setHead(Node node) {
      2      head = node;
      3      node.thread = null;
      4      node.prev = null;
      5 }

    这里的setHead( )方法可以看到head节点中的内容,head节点可以理解为正持有锁的节点,但实际里面的thread已经被置空。

      1 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
      2 	int ws = pred.waitStatus;
      3 	if (ws == Node.SIGNAL)
      4 	     return true;
      5 
      6 	//如果node的前置节点已经取消,则回收该取消节点
      7 	if (ws > 0) {
      8 	    do {
      9 		   node.prev = pred = pred.prev;
     10 	       } while (pred.waitStatus > 0);
     11 	       pred.next = node;
     12 	} else {
     13 	       //将其前置节点的状态改为SIGNAL
     14 	       compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
     15 	}
     16 	return false;
     17 }

    shouldParkAfterFailedAcquire( )方法是获取锁失败后应该进行阻塞(一个好名字多么重要),为进行阻塞设置了相关条件。该条件是要进行阻塞的节点的前置节点,其状态必须为SINGAL(只有前置节点是SINGAL状态,当前节点在前置节点释放锁后才会被唤醒)。如果当前节点已经取消,则将当前节点一直向前,直到链接到一个未被取消的节点,取消的节点被回收;若节点未被取消,也不是SINGAL状态,则更新其状态为SINGAL状态。

    我的质疑是,在前置节点是CANCELLED状态时,其向前链接的过程不是CAS进行的,安全吗?不过我也确实没想到具体的情景。

      1 private final boolean parkAndCheckInterrupt() {
      2      LockSupport.park(this);
      3      //若是中断,返回true;若是unpark,返回false
      4      return Thread.interrupted();
      5 }

    shouldParkAfterFailedAcquire( )方法准备好返回true后,则用parkAndInterrupt( )方法进行阻塞。其底层是调用LockSupport的park( )方法实现的。返回时,会有判断,若当前阻塞状态是其他线程通过中断完成的,则Thread.interrupted( )会返回true,并重置此状态,则当前线程在抢到锁返回后,会在acquire( )方法中执行interrupt( )方法,当前线程被中断(这里有两次中断,一次将线程从park状态唤醒,然后重置中断标志,抢到锁后,再调用一次中断,用来中断线程进行的任务);若是通过unpark( )结束阻塞,说明当前线程是被前置节点唤醒的,则Thread.interrupted( )会返回false,线程抢到锁后继续执行任务。

      1 public class Test6 {
      2     private final boolean parkAndCheckInterrupt() {
      3 	LockSupport.park(this);
      4 	return Thread.interrupted();
      5     }
      6 
      7 
      8     public static void main(String[] args) throws InterruptedException {
      9 	Test6 SPCK = new Test6();
     10 	Thread thread = new Thread(new Runnable() {
     11 		@Override
     12 		public void run() {
     13 			System.out.println("Before Park!");
     14 			if (SPCK.parkAndCheckInterrupt()) {
     15 				System.out.println("中断返回!");
     16 			}else {
     17 				System.out.println("Unpark返回!");
     18 			}
     19 		}
     20 		});
     21 
     22 	thread.start();
     23 	TimeUnit.SECONDS.sleep(1);
     24 	 //   thread.interrupt();
     25 	LockSupport.unpark(thread);
     26 	}
     27 }

    2.2.5 cancelAcquire方法

      1 private void cancelAcquire(Node node) {
      2     // Ignore if node doesn't exist
      3      if (node == null)
      4 	  return;
      5 
      6     //将其不再和线程关联
      7      node.thread = null;
      8 
      9     //将其链接到一个未被取消的前置节点后面
     10     //此时node.prve==pred,但pred.next不一定是node
     11      Node pred = node.prev;
     12      while (pred.waitStatus > 0)
     13 	    node.prev = pred = pred.prev;
     14 
     15      //保存pred.next,因为经过whille后,pred.next链接的可能是其他被取消的节点
     16       Node predNext = pred.next;
     17       node.waitStatus = Node.CANCELLED;
     18 
     19       //如果当前节点是tail,将其pred设为tail,
     20        if (node == tail && compareAndSetTail(node, pred)) {
     21 
     22       //将pred.next置位null
     23 	     compareAndSetNext(pred, predNext, null);
     24 	} else {
     25 	     int ws;
     26 
     27 	      //如果当前节点不是tail节点,也不是head的后继节点,
     28 	      //则将其前置节点置为SINGAL状态,并将前置节点的后继
     29 	      //节点链接到当前节点的后继节点
     30 	       if (pred != head &&
     31 		        ((ws = pred.waitStatus) == Node.SIGNAL ||
     32 		           (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
     33 		         pred.thread != null) {
     34 		    Node next = node.next;
     35 		    if (next != null && next.waitStatus <= 0)
     36 	       		 compareAndSetNext(pred, predNext, next);
     37 		} else {
     38 
     39 		     //如果当前节点是head的后继节点,则唤醒后继节点
     40 		     //注意此时pred.next还没有被取消,它是通过唤醒后继节点在
     41 		     //tryAcquire( )方法中进行的
     42 		      unparkSuccessor(node);
     43 		}
     44                 node.next = node; // help GC
     45 	}
     46 }

    此方法是在acquireQueued( )其中某处抛出异常时会调用的,作用是取消当前节点,主要分为三种情况:

    • 当前节点是tail节点
    • 当前节点不是tail节点,也不是head的后继节点
    • 当前节点是head的后继节点

    2.2.6 加锁流程

    clip_image018

    2.3 unlock方法

    2.3.1 release方法

      1 public void unlock() {
      2     sync.release(1);
      3 }
      4 
      5 public final boolean release(int arg) {
      6 
      7     //如果tryRelease中state状态为0,返回true
      8      if (tryRelease(arg)) {
      9 	   Node h = head;
     10 	   if (h != null && h.waitStatus != 0)
     11 	       //唤醒离头节点最近的未被取消到的节点
     12 		unparkSuccessor(h);
     13 	   return true;
     14 	}
     15      return false;
     16 }
     17 
      1 protected final boolean tryRelease(int releases) {
      2 
      3     //计算state更新后的值
      4     int c = getState() - releases;
      5     if (Thread.currentThread() != getExclusiveOwnerThread())
      6 	  throw new IllegalMonitorStateException();
      7     boolean free = false;
      8 
      9     //如果state待更新值为0,则包括重入的锁也被释放,可以清除锁被持有的标记
     10     if (c == 0) {
     11 	 free = true;
     12        	 setExclusiveOwnerThread(null);
     13      }
     14 
     15      //设置state状态,一旦设置为0,刚加入的线程可以立即进行争抢
     16      setState(c);
     17      return free;
     18 }
      1 private void unparkSuccessor(Node node) {
      2     int ws = node.waitStatus;
      3 
      4     //尝试设置当前节点状态为0
      5     if (ws < 0)
      6 	  compareAndSetWaitStatus(node, ws, 0);
      7 
      8      Node s = node.next;
      9      //如果当前节点后置节点被取消,则从tail向前,找到离当前节点最近的未被取消的节点,
     10      //然后唤醒该节点(此处并没有对那些取消的节点进行处理,而是唤醒后在tryAcquire()中处理的)
     11      if (s == null || s.waitStatus > 0) {
     12 	   s = null;
     13 	   for (Node t = tail; t != null && t != node; t = t.prev)
     14 		 if (t.waitStatus <= 0)
     15 		       s = t;
     16       }
     17       if (s != null)
     18 	   LockSupport.unpark(s.thread);
     19 }

    unlock( )方法调用了release( )方法,其中tryRelease( )方法是一个钩子方法,在Sync类中被重写,主要是更新state状态,在state状态为0时能够执行unparkSuccessor( )方法,唤醒线程。

    unparkSuccessor( )方法之前存在两个条件h != null && h.waitStatus != 0:

    • 因为对head和tail都是懒加载,所以如果head==null,可能只有一个线程工作,不存在其他线程争抢,阻塞队列也就根本没有初始化,没有工作;
    • h.waitStatus != 0,判断此条件是因为在tryAcquire( )方法中,阻塞线程时,其前置节点必须是SINGAL状态。若h.waitStatus != 0,则说明后置节点还没有被阻塞,不需要唤醒,直接就能去抢锁。

    2.4 对比非公平锁和公平锁及小结

    公平锁与非公平锁在实现上的不同方法:

      1 final void lock() {
      2      acquire(1);
      3 }
      4 
      5 protected final boolean tryAcquire(int acquires) {
      6      final Thread current = Thread.currentThread();
      7      int c = getState();
      8      if (c == 0) {
      9 
     10      //hasQueuedPredecessors(),如果当前队列除head外,前面还有节点,返回true
     11 	   if (!hasQueuedPredecessors() &&
     12 		      compareAndSetState(0, acquires)) {
     13 		 setExclusiveOwnerThread(current);
     14 		  return true;
     15 	    }
     16       }
     17       else if (current == getExclusiveOwnerThread()) {
     18 	         int nextc = c + acquires;
     19 		 if (nextc < 0)
     20 		        throw new Error("Maximum lock count exceeded");
     21 		 setState(nextc);
     22 		 return true;
     23        }
     24        return false;
     25 }

    可以看到,非公平主要在两个地方上:

    • 一是在lock( )方法实现上,非公平锁先进行了一次抢锁操作,而从上一个线程释放锁,到唤醒下一个节点,是需要时间的,此时新入新城更可能抢到锁;
    • 二是tryAcquire( )方法上,非公平锁还是执行直接抢锁,抢不到再入队。而公平锁实现上,会先判断当前队列中是否有等待锁的节点,没有就取锁,有则入队(乖乖排队,完全不竞争一下)。

    从ReentrantLock来看AQS,整个“锁”的过程,我们并没有看到线程拿到一个切实存在“锁”,只是以state的状态进行了表示,以ExclusiveOwnerThread进行了标识,实际上是当一个线程执行操作时,其他线程被阻塞在队列中(当然流程上讲,synchornized的实现与之相差不大)。

    三、Condition接口实现

    上面我们用到了await( )和signal( )实现了两个简单的生产者消费者模型,其都依赖于Condition接口。Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待set(wait-set)。这也是这两个生产者消费者模型不同实现的原因,我们从源码出发:

    3.1 条件队列和同步队列

    首先我们需要区分这两个队列,在同步队列中,底层是回归到Node类的。在Sync队列中,如图3.1(图来源于网络),我们使用到节点中的thread、waitStatus、prve和next属性,nextWaiter属性仅仅被赋为null,表示独占,但在条件队列中,它具有更重要的作用:

    clip_image021

                                                                                          图3.1 阻塞队列

    每new一个Condition对象,就会产生一个对应的Condition队列,这个队列就是用nextWaiter进行串联的一个单向队列。因此实际上在条件队列中仅会用到thread、waitStatus和nextWaiter属性。

    此时我们再来看上面的两个生产者消费者模型,两者的主要区别在于对Condition对象的使用。第一个使用了一个Condition,两个线程进入同一个条件队列:

    clip_image022

    第二个使用了两个Condition,两个线程进入不同Condition下的条件队列:

    clip_image023

    到这里就可以理解“Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象”,将其与synchornized关键字对比来看,使用synchornized时需要一个对象,对象关联着一个monitor对象,调用wait( )方法,线程进入等待池,notify( )后则进入锁池竞争锁,即监视器方法与该对象monitor配合使用;而使用Lock,可以产生多个Condition,不同Condition使用await( )和singal( )将阻塞和唤醒不同Condition队列中到的线程。将Condition类比为monitor中的等待池,就好像Condition对其进行了细分,使得await( )和singal( )能对应每个不同Condition,而不用与wait( )和notify( )仅作用于monitor中的唯一的等待池。因此使用Condition在受保证的通知排序,或者在执行通知时不需要保持一个锁场景下很受用。

    同步队列与条件队列有什么联系呢?事实上对这两个队列的维护是独立的,不过两者之间和一定联系。如同使用synchornized时,等待池中的线程被唤醒后进入锁池竞争锁。和synchornized类似,Condition队列中的线程被singal( )后,会进入Sync队列中等待锁。需要注意的是,被唤醒的队列不是简单的直接链接在Sync队列后面,而是一个个进行转移。这种方式一方面更安全,另一个方面则是因为Condition队列中是使用nextWaiter链接的单向链表,而Sync队列是使用prev和next链接的双向链表。

    3.2 ConditionObject

    Condition在AQS中是通过ConditionObject实现的,只有两个核心属性,标识队头和队尾。

      1 public class ConditionObject implements Condition, java.io.Serializable {
      2 
      3      /** First node of condition queue. */
      4       private transient Node firstWaiter;
      5      /** Last node of condition queue. */
      6       private transient Node lastWaiter;
      7 
      8      /** Mode meaning to reinterrupt on exit from wait */
      9       private static final int REINTERRUPT =  1;
     10      /** Mode meaning to throw InterruptedException on exit from wait */
     11       private static final int THROW_IE    = -1;
     12 
     13      /**
     14        * Creates a new <tt>ConditionObject</tt> instance.
     15        */
     16       public ConditionObject() { }
     17 }

    这里我们先分析一下,入队和出队的情况:线程是在调用await( )方法后进入Condition队列的,因此进入Condition队列之前节点是已经获取到锁的。在调用await( )后,当前线程释放掉锁,进入Condition队列。singal( )或中断后,节点从await( )继续执行,此时节点就会进入Sync队列再次竞争锁,所以唤醒后从await( )继续执行时线程一定是获取到锁的。

    3.3 await方法

      1 public final void await() throws InterruptedException {
      2    //线程已被中断,没有加入队列的必要
      3    if (Thread.interrupted())
      4 	 throw new InterruptedException();
      5 
      6     //将当前线程封装成Node,加入Condition队列
      7     Node node = addConditionWaiter();
      8 
      9     //释放当前线程的锁,返回锁释放前线程的state
     10     //释放锁后,锁被新的线程持有,释放锁的线程的节点就会从Sync中移除
     11     int savedState = fullyRelease(node);
     12     int interruptMode = 0;
     13 
     14     //判断该节点是否在Sync队列中,在说明唤醒后已被转移至Sync队列
     15     //不在说明刚await()从Sync队列移除了
     16     while (!isOnSyncQueue(node)) {
     17 	    LockSupport.park(this);
     18 	    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
     19 		    break;
     20      }
     21 
     22      if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
     23 	     interruptMode = REINTERRUPT;
     24      if (node.nextWaiter != null) // clean up if cancelled
     25              unlinkCancelledWaiters();
     26      if (interruptMode != 0)
     27 	     reportInterruptAfterWait(interruptMode);
     28 }

    while以后的部分主要作用是在Condition队列中阻塞当前线程,并在唤醒后继续执行park( )后的代码。但唤醒的方式可以是singal( ),也可能是由于中断引起的,所以while之后的部分对此作了判断,具体在3.5进行详细讨论。

    3.3.1 addConditionWaiter和fullyRelease方法

      1 private Node addConditionWaiter() {
      2      Node t = lastWaiter;
      3 
      4      //如果尾节点被取消,则清除尾节点
      5      if (t != null && t.waitStatus != Node.CONDITION) {
      6 	   //删除队列中已经取消的节点
      7 	   unlinkCancelledWaiters();
      8            t = lastWaiter;
      9       }
     10 
     11       //将当前线程封装成Condition状态的节点,加入队列尾部
     12       Node node = new Node(Thread.currentThread(), Node.CONDITION);
     13       if (t == null)
     14 	    firstWaiter = node;
     15       else
     16 	    t.nextWaiter = node;
     17       lastWaiter = node;
     18       return node;
     19 }
      1 //从头遍历,删除状态不为Condition的节点
      2 private void unlinkCancelledWaiters() {
      3      Node t = firstWaiter;
      4      Node trail = null;
      5      while (t != null) {
      6 	     Node next = t.nextWaiter;
      7 	     if (t.waitStatus != Node.CONDITION) {
      8 		    t.nextWaiter = null;
      9 		    if (trail == null)
     10 			   firstWaiter = next;
     11 		    else
     12 			   trail.nextWaiter = next;
     13 
     14 		    if (next == null)
     15 			    lastWaiter = trail;
     16 	      }
     17                else
     18 		     trail = t;
     19 	       t = next;
     20 	}
     21 }

    从这里我们可以看到Sync队列和Condition队列的不同:

    • Sync队列有dummy节点,Condition队列没有
    • Sync队列初始状态为0,Condition队列初始状态为Condition
    • Sync队列有是一个双向队列,Condition队列是一个单向队列
      1 final int fullyRelease(Node node) {
      2      boolean failed = true;
      3      try {
      4 	  //获取节点state
      5 	  int savedState = getState();
      6 
      7 	   //释放锁
      8 	   if (release(savedState)) {
      9 		  failed = false;
     10 		  return savedState;
     11 	    } else {
     12 		  throw new IllegalMonitorStateException();
     13 	    }
     14       } finally {
     15 	    //失败,则取消该节点
     16 	    if (failed)
     17 		  node.waitStatus = Node.CANCELLED;
     18 	}
     19 }

    await( )方法的目的就是释放掉当前线程持有的锁,也就是靠fullyRelease( )方法完成的。该方法获取当前线程state,该属性在ReentrantLock中,表示线程持有锁的状态(包括重入),在release( )方法中传入state值,即一次释放掉所有的锁。

    3.3.2 isOnSyncQueue和findNodeFromTail方法

      1 final boolean isOnSyncQueue(Node node) {
      2     //如果waitStatus为CONDITION,或前置节点为null,说明已不在阻塞队列中
      3     if (node.waitStatus == Node.CONDITION || node.prev == null)
      4        	  return false;
      5     //如果当前节点有后继节点,肯定在队列中
      6     if (node.next != null)
      7 	  return true;
      8 
      9     //排除上面情况,队尾节点prev不为null,后继节点为null,再从队尾向前找
     10     return findNodeFromTail(node);
     11 }
      1 private boolean findNodeFromTail(Node node) {
      2      Node t = tail;
      3      for (;;) {
      4 	  if (t == node)
      5  		return true;
      6 	  if (t == null)
      7 		return false;
      8 	t = t.prev;
      9       }
     10 }

    首先在该调用中,此方法传入的Node是addWaiter( )方法返回的Condition队列中使用的节点,因此该节点一定是CONDITION或CANCELLED状态,且其prev字段一定是null,而在Sync队列中,节点状态一定是0、SINGAL或CANCELLED状态,只有head节点的prev是null,但head状态节点一定是SINGAL状态。

    3.4 singal方法

      1 public final void signal() {
      2     //执行singal()的线程是否持有锁,未持有就抛出异常
      3     if (!isHeldExclusively())
      4 	  throw new IllegalMonitorStateException();
      5     Node first = firstWaiter;
      6     //若条件队列不为null,执行doSingal()
      7     if (first != null)
      8 	   doSignal(first);
      9 }
      1 public final void signalAll() {
      2 	if (!isHeldExclusively())
      3 		throw new IllegalMonitorStateException();
      4 	Node first = firstWaiter;
      5 	if (first != null)
      6 		doSignalAll(first);
      7 }
    signalAll

    首先执行唤醒操作的必须是已经获取到锁的线程,不是就会抛出异常;然后该Condition条件队列必须存在,才能执行唤醒操作。

    3.4.1 doSingal和doSingalAll方法

      1 //作用节点从条件队列中断开,即断开nextWaiter
      2 private void doSignal(Node first) {
      3     do {
      4 	 //断开上一个节点和下一个节点
      5          //若遍历到最后一个节点,将lastWaiter节点置为null
      6 	 if ( (firstWaiter = first.nextWaiter) == null)
      7 		 lastWaiter = null;
      8 		 first.nextWaiter = null;
      9 
     10          //将断开的节点转移至阻塞队列,转移失败则继续转移下一个节点
     11          //直到有一个转移成功的节点或条件队列为null
     12         } while (!transferForSignal(first) &&
     13 	           (first = firstWaiter) != null);
     14 }
      1 private void doSignalAll(Node first) {
      2 	lastWaiter = firstWaiter = null;
      3 	do {
      4 		Node next = first.nextWaiter;
      5 		first.nextWaiter = null;
      6 		transferForSignal(first);
      7 		first = next;
      8 	} while (first != null);
      9 }
    doSignalAll

    这两个方法主要作用是将Condition队列中的Node,一个一个转移至Sync队列。doSingal( )从头遍历,直到有一个节点成功转移到Sync队列后结束,doSingalAll( )将所有节点转移至Sync队列后结束。

    3.4.2 transferForSingal方法

      1 final boolean transferForSignal(Node node) {
      2     //在将节点插入阻塞队列之前,将节点状态设置为0
      3     if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
      4 	    return false;
      5 
      6     //将节点插入阻塞队列,返回的p是当前节点插入之前的tail
      7     Node p = enq(node);
      8     int ws = p.waitStatus;
      9 
     10     //如果p被取消或者不能将该节点状态设置为SINGAL,就唤醒当前状态
     11     if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
     12            LockSupport.unpark(node.thread);
     13     return true;
     14 }

    该方法将节点从Condition队列转移至Sync队列,首先在doSingal( )方法中节点已经断开nextWaiter(为null),尝试将节点状态设置为0,若失败,则说明该节点是已经被取消,返回false后被回收。

    若状态设置成功,则将其插入Sync队列,这里enq( )方法返回的是之前的tail节点,然后有对该tail节点有一个判断。出现这个判断的原因在于enq( )仅仅是将节点插入队列,并没有对之前的节点进行校验,则返回的tail节点可能是已经取消的,或者设置状态失败。此情况下需要唤醒该节点,唤醒后该节点会继续执行await( )的部分,从而进入到acquireQueued( )方法,就能清除CANCELLED状态的节点,并将前置节点设置为SINGAL,接着尝试获取锁,获取不到又被阻塞。

    因为await( )和singal( )方法在lock和unlock中执行,unlock后一定会唤醒阻塞队列中的节点。

    3.5 await被唤醒后

    当调用await( )时,线程进入条件队列,线程所在阻塞队列节点被移除,然后进入while后被阻塞。当被唤醒后线程继续从park( )后执行,然后该节点都会从Condition队列转移到Sync队列,并调用acquireQueued( )方法“阻塞式”竞争锁,因此await( )方法返回时,当前线程一定是持有锁的。

    但线程被唤醒可能是由于singal( )引起,也可能是中断引起的(但无论那种原因都会执行第一段话的过程),为此我们将线程被唤醒和线程去抢锁看成两个时间点,则可划分为:

    1、整个过程,从未发生中断,唤醒由singal( )引起

    2、过程中发生了中断:

    • 中断发生在singal( )之前,即唤醒由中断引起
    • 中断发生在signal之后,此时又有两种情况:

        a. 中断发生在singal( )之后,但在获取锁之前(即在刚唤醒后发生中断)

              b. 中断发生在singal( )之后,但在获取锁时(即在抢锁过程中发生中断)

    讨论之前,我们需要明确,中断机制只是改变了中断标志,并不会立即终止程序运行,它的作用在于对中断信号的处理上,而await( )方法调用的acquireQueued( )方法,该方法并没有处理中断信号,只是对保存了中断信号状态,交由上层函数处理,即这里的await( )。针对出现的情况,在Condition提供了两个属性,来表示三种情况:

    0:代表整个过程中一直没有中断发生,唤醒由singal( )引起。

    THROW_IE:表示退出await()方法时需要抛出InteruptedException,这种模式对应于中断发生在signal之前。

    REINTERRUPT:表示退出await()方法时只需要再自我中断以下, 这种模式对应于中断发生在signal之后, 即中断来的太晚了。

    接下来我们针对几种情况做不同讨论:

    3.5.1 case1:整个过程未发生中断

      1 //检查等待过程中的中断情况
      2 private int checkInterruptWhileWaiting(Node node) {
      3     //若线程未中断,则返回0
      4     return Thread.interrupted() ?
      5 	//若线程中断,则要判断该中断发生在哪个时间节点
      6 	(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
      7 	 0;
      8 }

    这种情况比较简单,Thread.interrupted( )返回false,checkInterruptWhileWaiting( )方法返回0,即interruptMode==0,由于是singal( )唤醒,则线程已经转移至Sync队列,跳出循环。

      1 public final void await() throws InterruptedException {
      2 	if (Thread.interrupted())
      3 		throw new InterruptedException();
      4 
      5 	Node node = addConditionWaiter();
      6 	int savedState = fullyRelease(node);
      7 	int interruptMode = 0;
      8 
      9 	while (!isOnSyncQueue(node)) {
     10 		LockSupport.park(this);
     11 		if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
     12 			break;
     13 	}
     14 
     15 	if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
     16 		interruptMode = REINTERRUPT;
     17 	if (node.nextWaiter != null) // clean up if cancelled
     18 		unlinkCancelledWaiters();
     19 	if (interruptMode != 0)
     20 		reportInterruptAfterWait(interruptMode);
     21 }

    接下来就会执行acquireQueued( )方法,因为整个过程都未发生中断,则acquireQueued( )方法返回false,不会继续执行第一个if,以及后面的两个if。值得注意的是,acquireQueued( )方法中传入的savedState是之前解锁时保留的,即当初解开多少锁,就再次获取多少锁。

    3.5.2 case2:中断发生在singal之前

    此情况说明该次唤醒是由中断引起的,则之后再调用singal( )就“晚了”(因为该线程已经通过非singal( )方法下进入Sync队列,Condition队列中已经没有该节点或singal( )时的CAS操作节点状态时会失败)。继续看代码:

      1 private int checkInterruptWhileWaiting(Node node) {
      2 	return Thread.interrupted() ?
      3 		(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
      4 		0;
      5 }
      1 //将节点转移至阻塞队列(发生中断时,不是由signal()进行的转移)
      2 final boolean transferAfterCancelledWait(Node node) {
      3      if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
      4 	  enq(node);
      5 	  return true;
      6       }
      7       while (!isOnSyncQueue(node))
      8 	       Thread.yield();
      9       return false;
     10 }

    此时Thread.interrupted返回true,执行transferAfterCancelledWait( )方法。因为通过signal( )方法,节点从将从Condition队列转移至Sync队列,节点状态会置为0。因为中断唤醒的,所以节点还没有进行转移,所以if中的CAS会执行成功,然后enq( )方法将节点插入Sync队列,返回true。注意此时nextWaiter并没有断开,仍在Condition队列中。

      1 while (!isOnSyncQueue(node)) {
      2 	LockSupport.park(this);
      3 	if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
      4 		break;
      5 }
      6 
      7 if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
      8 	interruptMode = REINTERRUPT;
      9 if (node.nextWaiter != null) // clean up if cancelled
     10 	unlinkCancelledWaiters();
     11 if (interruptMode != 0)
     12 	reportInterruptAfterWait(interruptMode);

    返回true,checkInterruptWhileWaiting( )方法即会返回THROW_IE==-1,跳出循环。回到await( ),当前线程执行acquireQueued( )方法“阻塞式”获取锁,因为发生了中断该方法最终会返回true,但因为继续不会执行第一个if。因为nextWaiter并未断开,则执行第二个if,通过unlinkCancelledWaiters( )方法断开所有不是CONDITION状态的节点,即将当前节点从Condition队列中移除。

      1 private void reportInterruptAfterWait(int interruptMode)
      2      throws InterruptedException {
      3      //值为THROW_IE抛出异常
      4      if (interruptMode == THROW_IE)
      5 	    throw new InterruptedException();
      6      //值为REINTERRUPT,再自己进行一次中断
      7      else if (interruptMode == REINTERRUPT)
      8 	    selfInterrupt();
      9 }

    接着执行第三个if,这里对两种情况的处理是,值为THROW_IE抛出异常;值为REINTERRUPT,再自己进行一次中断,这个处理方式和acquie( )方法对执行acquireQueued( )时发生中断时的处理方式相同。

    3.5.3 case3:中断发生在singal之后

    这种情况下有两种情况:

    (1)中断发生在singal( )之后,但在acquireQueued( )之前发生

      1 private int checkInterruptWhileWaiting(Node node) {
      2 	return Thread.interrupted() ?
      3 		(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
      4 		0;
      5 }
      1 final boolean transferAfterCancelledWait(Node node) {
      2 	if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
      3 		enq(node);
      4 		return true;
      5 	}
      6 	while (!isOnSyncQueue(node))
      7 		Thread.yield();
      8 	return false;
      9 }

    此情况下Thread.interrupted( )返回true,执行transferAfterCancelledWait( ),因为在中断前执行了singal( ),所以不会执行if,最终返回false。这里的while是因为singal( )方法是另一个线程执行的,所以可能singal( )方法才改变节点状态,还没有将节点加入Sync队列,因此需要自旋等待,直到该节点加入阻塞队列。

      1 while (!isOnSyncQueue(node)) {
      2 	LockSupport.park(this);
      3 	if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
      4 		break;
      5 }
      6 
      7 if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
      8 	interruptMode = REINTERRUPT;
      9 if (node.nextWaiter != null) // clean up if cancelled
     10 	unlinkCancelledWaiters();
     11 if (interruptMode != 0)
     12 	reportInterruptAfterWait(interruptMode);

    回到await( ),最终interruptMode为REINTERRUPT,跳出循环。因为acquireQueued( )方法中没有产生中断,则会返回false,不会再执行第一个if。转移操作是由singal( )进行的,也不会执行第二个if。最后根据interruptMode的值,线程自己再执行了一次中断操作。

    (2)中断发生在singal( )之后,是在acquireQueued( )方法中发生的中断。

    此种情况下,跳出循环时,interruptMode值为0。但在acquireQueued( )中发生了中断,所以返回true,会接着执行第一个if,将interruptMode的值赋为REINTERRUPT,接着执行与(1)相同的操作。

    3.6 await方法小结

    clip_image039

    这是await( )的大致执行流程,这里对中断产生的唤醒也去进行了抢锁操作,但实际上此时即使抢到锁,也会发现条件并不满足,所以还是再次进入await( ),这样就会浪费资源。这种情况我们可以选择使用awaitUninterruptibly( )方法,该方法会忽略中断,只有接受singal( )方法的唤醒。

    posted @ 2020-11-07 13:10  Aidan_Chen  阅读(622)  评论(0编辑  收藏  举报