ReentrantLock

相对于 synchronized 它具备如下特点

  • 可打断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量

与 synchronized 一样,都支持可重入

基本用法

// 获取锁
reentrantLock.lock();
try {
 // 临界区
} finally {
 // 释放锁
 reentrantLock.unlock();
}

可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁

如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

可打断

我在进入 Blocked list 等待锁的过程中,别的线程可以用 interupt() 方法打断我的等待(sychronized不可打断,reentrantLock.lock() 不可打断,reentrantLock.lockInterruptibly() 才可以打断)

被打断后会抛出 InteruptException 异常

可以说是一种 被动地避免死等的手段

锁超时

lock.tryLock() 没有参数的话,如果没获得锁,立刻返回false

有超时参数的话,等待一段时间后还没获得锁,再返回false

ReentrantLock lock = new ReentrantLock();
  Thread t1 = new Thread(() -> {
   log.debug("启动...");
   try {
     if (!lock.tryLock(1, TimeUnit.SECONDS)) {
       log.debug("获取等待 1s 后失败,返回");
       return;
     }
     } catch (InterruptedException e) {
       e.printStackTrace();
     }
     try {
       log.debug("获得了锁");
     } finally {
       lock.unlock();
     }
  }, "t1");

  lock.lock();
  log.debug("获得了锁");
  t1.start();
  try {
   sleep(2);
  } finally {
  lock.unlock();
}

用 tryLock 解决哲学家就餐问题

 @Override
 public void run() {
   while (true) {
     // 尝试获得左手筷子
     if (left.tryLock()) {
       try {
         // 尝试获得右手筷子
         if (right.tryLock()) {
           try {
             eat();
           } finally {
             right.unlock();
           }
         }
       } finally {
         left.unlock();
       }
     }
   }
 }

公平锁

非公平锁:之前持有锁的线程释放了锁,那么所有在 entryList 的线程一拥而上,谁先抢到了谁就是主人,而不是谁先到谁就先得

公平锁:阻塞队列里的线程抢锁时,是按进入阻塞的时间先入先得的顺序获得锁

本意是为了减少饥饿问题,但是 tryLock 可能更好。一般不会使用公平锁,因为会降低并发度(后面原理会讲?)

ReentrantLock 默认是非公平锁,如果要是公平锁要显式设置

ReentrantLock lock = new ReentrantLock(true);

条件变量

synchronized 中也有条件变量,就是获取了 sychronized 获取锁后可以调用 .wait() ,当条件不满足时进入 waitSet 等待

ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比 synchronized 是那些不满足条件的线程都在一间休息室等消息

而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒

与 sychronized 配合的 wait() 类似,使用要点:

  • await 前需要获得 ReentrantLock 锁
  • await 执行后,会释放锁,进入 conditionObject 等待
  • await 的线程被 .signal() .signalAll() 唤醒(或打断、或超时)时重新竞争 lock 锁
  • 竞争 lock 锁成功后,从 await 后继续执行

用法:

lock 有一点就是一定要在 finally 里释放,lock 和 unlock 成对出现

static ReentrantLock roomlock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitbreakfastQueue = lock.newCondition();
static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;

public static void main(String[] args) {
   new Thread(() -> {
     try {
       roomlock.lock();
       while (!hasCigrette) {
        try {
          waitCigaretteQueue.await();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
       }
       log.debug("等到了它的烟");
     } finally {
       lock.unlock();
     }
   }).start();
 
  new Thread(() -> {
     try {
       roomlock.lock();
       while (!hasBreakfast) {
         try {
           waitbreakfastQueue.await();
         } catch (InterruptedException e) {
           e.printStackTrace();
         }
       }
       log.debug("等到了它的早餐");
    } finally {
       lock.unlock();
     }
   }).start();

   sleep(1);
   sendBreakfast();
   sleep(1);
   sendCigarette();
}

private static void sendCigarette() {
   roomlock.lock();
   try {
     log.debug("送烟来了");
     hasCigrette = true;
     waitCigaretteQueue.signal();
   } finally {
     lock.unlock();
   }
}

private static void sendBreakfast() {
   roomlock.lock();
   try {
     log.debug("送早餐来了");
     hasBreakfast = true;
     waitbreakfastQueue.signal();
   } finally {
     lock.unlock();
  }
}

 

使用案例-交替打印

abcabcabcabcabcabc

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class TestThreadJTPrint2 {

    public static void main(String[] args) {
        // 循环次数, 五次。做为 AwaitSignal 的构造方法入参传入。AwaitSignal 继承 ReentrantLock
        AwaitSignal awaitSignal = new AwaitSignal(5);
        // 每个线程都有自己的休息室
        Condition aCondition = awaitSignal.newCondition();
        Condition bCondition = awaitSignal.newCondition();
        Condition cCondition = awaitSignal.newCondition();
        Thread t1 = new Thread(() -> {
            // 打印"a", myCondition 是 aCondition, nextCondition 是 bCondition
            awaitSignal.print("a", aCondition, bCondition);
        });

        Thread t2 = new Thread(() -> {
            awaitSignal.print("b", bCondition, cCondition);
        });

        Thread t3 = new Thread(() -> {
            awaitSignal.print("c", cCondition, aCondition);
        });
        
        // 依次 start 的时候,都会先去自己的休息室等待
        t1.start();
        t2.start();
        t3.start();
        
        // t1 t2 t3 启动后,要等等待一段时间,等它们都到各自的休息室准备好。再由主线程唤醒a休息室里的a线程
        // 不然会有错误
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // t1 t2 t3 依次 start 的时候,都会先去自己的休息室等待
        // 所以要由主线程来先唤醒休a休息室里的线程。主线程也要先 lock 才能 signal()
        awaitSignal.lock();
        try {
            // 注意是 signal() 不是 notifyAll()
            aCondition.signal();
        }
        finally {
            // unlock 一定要在 finally 里
            awaitSignal.unlock();
        }
    }

    // 继承了 ReentrantLock 后 lock() 就相当于 lock(this)
    public static class AwaitSignal extends ReentrantLock {
        private int loopNumber;
        AwaitSignal(int loopNumber) {
            this.loopNumber = loopNumber;
        }
        public void print(String printContent, Condition myCondition, Condition nextCondition) {
            for (int i=0;i<loopNumber;i++) {
                // 因为 extends ReentrantLock,所以可以直接有这个lock方法,相当于lock住了this
                lock();
                try {
                    // 打印前都先去自己的休息室休息。有人来唤醒才会去下面真正打印
                    myCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 真正打印
                System.out.println(printContent);
                // 唤醒下一个休息室的线程。注意是 signal() 不是 notifyAll()
                nextCondition.signal();
            }
        }
    }
}

 

 

原理

1.非公平锁加锁(CAS 修改 AQS 对象的 state 0-1表示锁。失败 park 并添加到阻塞链表)

public void lock() {
    sync.lock();
}

lock()

// 非公平锁
static final class NonfairSync extends Sync {
        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
          // 加锁成功,设置锁 owner 为当前线程就结束了。直接返回
                setExclusiveOwnerThread(Thread.currentThread());
           // 加锁失败
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    } 

acquire

会再试着加锁一次,如果再失败,就会进入等待队列

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}

addWaiter 

cas 获取锁(!tryAcquire(arg))失败后:addWaiter 将等待的线程加入链表中

参数:Node.EXCLUSIVE 表示当前是独占模式,即只有一个线程能够访问资源

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
       if (pred != null) {
            // 当前节点的 prev 指向之前的尾部节点 pred
            node.prev = pred;
            // CAS 设置链表 tail 变量为当前节点 node,期待前值为【pred】
            if (compareAndSetTail(pred, node)) {
                // 之前的尾部节点 pred 的 next 指向当前节点
                pred.next = node;
                return node;
            }
        }
        // pred==null 也就是链表没初始化
        // 或者 CAS 设置 tail 为当前节点失败时 进入
        enq(node);
        return node;
} 

enq

private Node enq(final Node node) {
      // 循环进行尝试
        for (;;) {
       // 每次循环获取最新的尾部节点
            Node t = tail;
       // 尾部节点为空,即链表还没初始化的情况
            if (t == null) { // Must initialize
                // CAS 将链表 head 变量设为当前节点,期待前值是 null
                if (compareAndSetHead(new Node()))
            // 新链表,tail 也为当前节点
                    tail = head;
            } 
        // addWaiter CAS 将链表的 tail 变量设为当前节点 失败时(也就是说 tail 被别的线程改掉了),会第一次走到这里,重新尝试
        else {
          // 当前节点 prev 指向最新的尾部节点 t
                node.prev = t;
          // CAS 将链表的 tail 变量设为当前节点,期待前值为 t
                if (compareAndSetTail(t, node)) {
             // 之前的尾部节点 t 的 next 指向当前节点
                    t.next = node;
            // 一定是添加到队列成功了,才会退出循环返回
return t; } } } }

acquireQueued

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
          // 当前节点的前驱节点
                final Node p = node.predecessor();
          // 如果当前节点前一个节点是头节点,说明当前节点是等待队列里的第一个(头节点是虚的)
          // 此时再尝试获取一次锁 tryAcquire
if (p == head && tryAcquire(arg)) {             // 如果获取成功了。把当前节点设为虚头节点(令 head=当前节点,并且 thread prev 置为 null) setHead(node);             // 之前的头节点 p.next = null; // help GC             // 失败标志位 failed = false;             // 返回中断标志位 return interrupted; }           // 检查当前节点是否需要阻塞 if (shouldParkAfterFailedAcquire(p, node) &&             // 在这里 park 等待。 parkAndCheckInterrupt())             // interrupted = true; } } finally { if (failed) cancelAcquire(node); } }

parkAndCheckInterrupt

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

shouldParkAfterFailedAcquire

注意不要混淆两个 state

state 是一个 AQS 实例中的一个属性,可以用 0 没被锁,1 加了锁

在等待队列中的节点都是没获取到锁的线程,Node 有一个属性叫 waitStaus,0 是初始状态

        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;

检查是否需要阻塞

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
     // 获取前一个节点的状态
        int ws = pred.waitStatus;
     // 如果等待队列中前一个节点在阻塞,所以这个也阻塞 park
        if (ws == Node.SIGNAL)
            return true;
     // >0 表示取消状态(>0的只有一个就是0取消)
if (ws > 0) { // 上一个节点取消, 那么重构删除前面所有取消的节点, 返回到外层循环重试 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; }
     // 前驱节点的 waitStatus 是【初始默认状态 0】,那么将前驱节点改为 -1。
     // -1 表示它有责任唤醒它的后继节点,因为要 park,所以必须保证可以有人来唤醒它,这个来唤醒它的就是前驱节点

     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); } return false; }

可以看到失败后,还会进行好几次获取锁的尝试,在第四次失败后,才会真正 Park

双向链表,每个等待线程由它的前驱节点线程唤醒,所以前驱节点的 waitStatus 都是 -1

 

2.非公平锁解锁(锁重入计数减为0,改锁状态。unpark 后继节点,后继节点获得锁)

 锁重入计数减为0,改 AQS 对象状态,exclusiveThread 置为 null 。 unpark 后继节点

// Sync 继承自 AQS
static final class NonfairSync extends Sync {
 // 解锁实现
 public void unlock() {
   sync.release(1);
 }
 
 // AQS 继承过来的方法, 方便阅读, 放在此处
 public final boolean release(int arg) {
   // 尝试释放锁, 进入 ㈠
   if (tryRelease(arg)) {
     // 队列头节点 unpark
     Node h = head; 
     if (
         // 队列不为 null 
        h != null && // waitStatus == Node.SIGNAL (-1)需要 unpark        h.waitStatus != 0     ) {       // 释放它的后继节点 unpark AQS 中等待的线程, 进入 ㈡       unparkSuccessor(h);     }     return true;   }   return false; } // ㈠ Sync 继承过来的方法, 方便阅读, 放在此处 protected final boolean tryRelease(int releases) {   // state--   int c = getState() - releases;   if (Thread.currentThread() != getExclusiveOwnerThread())      throw new IllegalMonitorStateException();   boolean free = false;   // 支持锁重入, 只有 state 减为 0, 才释放成功   if (c == 0) {     free = true;     setExclusiveOwnerThread(null);   }   setState(c);   return free; } // ㈡ AQS 继承过来的方法, 方便阅读, 放在此处 private void unparkSuccessor(Node node) {   // 如果状态为 Node.SIGNAL 尝试重置状态为 0   // 不成功也可以   int ws = node.waitStatus;   if (ws < 0) {     compareAndSetWaitStatus(node, ws, 0);   }    // 找到需要 unpark 的节点, 但本节点从 AQS 队列中脱离, 是由唤醒节点完成的    Node s = node.next;    // 不考虑已取消的节点, 从 AQS 队列从后至前找到队列最前面需要 unpark 的节点   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); } }

unpark 后继节点后,后继节点继续 for 循环,会获得锁

  final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
          // 解锁 unpark 后,下一个循环在这里获得锁
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
          // 在 parkAndCheckInterrupt 里 park 住
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    private final boolean parkAndCheckInterrupt() {
     // 在这里 park 住
        LockSupport.park(this);
        return Thread.interrupted();
    }

 

3.可重入

有一个重入计数

  • 加锁时:如果加锁失败,判断锁的 owner 是否是当前线程,如果是,重入计数加一。
  • 解锁时:释放一次,计数减一。只有重入计数减为0,才代表解锁成功
static final class NonfairSync extends Sync {
 // ...
 
 // Sync 继承过来的方法, 方便阅读, 放在此处
 final boolean nonfairTryAcquire(int acquires) {
   final Thread current = Thread.currentThread();
   int c = getState();
   if (c == 0) {
     // 加锁,CAS 修改 AQS 对象的 state 从0到1。acquires 为1
     if (compareAndSetState(0, acquires)) {
       setExclusiveOwnerThread(current);
       return true;
     }
   }
   // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
   else if (current == getExclusiveOwnerThread()) {
     // state++(acquires=1)
     int nextc = c + acquires;
     if (nextc < 0) // overflow
       throw new Error("Maximum lock count exceeded");
       setState(nextc);
       return true;
     }
     return false;
   }
}
// Sync 继承过来的方法, 方便阅读, 放在此处 protected final boolean tryRelease(int releases) {   // state-- (释放一次,计数减一)   int c = getState() - releases;   if (Thread.currentThread() != getExclusiveOwnerThread())     throw new IllegalMonitorStateException();   boolean free = false;   // 支持锁重入, 只有 state 减为 0, 才释放成功   if (c == 0) {     free = true;     setExclusiveOwnerThread(null);   }    setState(c);   return free; } }

 

4.可打断(park 时被打断了,不可打断-忽略等获取到锁再重新打断;可打断-直接抛出打断异常)

不可打断模式

默认情况下是不可打断的。在此模式下,即使它被打断,仍会驻留在 AQS 队列中,仅会设置一个一个打断标志,一直要等到获得锁后根据打断标志得知自己被打断了,才真正打断

// Sync 继承自 AQS
static final class NonfairSync extends Sync {
 // ...
 
 private final boolean parkAndCheckInterrupt() {
   // 如果打断标记已经是 true, 则 park 会失效
   LockSupport.park(this);
   // 如果被打断了,会停止 park 走到这里。interrupted 返回打断标记,并清除打断标记
   return Thread.interrupted();
 }
 
 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;
         failed = false;
         // 还是需要获得锁后, 才能返回打断状态
         return interrupted;
       }
       if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
         // 被打断后 parkAndCheckInterrupt 返回 true,走到这里。只是设置了标记变量,其它什么都没干,没有抛出打断异常
         interrupted = true;
       }
     }
   } finally {
     if (failed)
       cancelAcquire(node);
   }
 }
 
 public final void acquire(int arg) {
    // 上面 acquireQueued 返回打断标记为 true 后,即被打断的线程在一直排队等待获得锁以后。会进到里面,重新打断一次     
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {       // 如果打断状态为 true       selfInterrupt();     } } static void selfInterrupt() {    // 重新产生一次中断   Thread.currentThread().interrupt(); } }

可打断模式

park 时被打断了,直接抛出打断异常

static final class NonfairSync extends Sync {
 public final void acquireInterruptibly(int arg) throws InterruptedException {
   if (Thread.interrupted())
     throw new InterruptedException();
   // 如果没有获得到锁, 进入 ㈠
   if (!tryAcquire(arg))
     doAcquireInterruptibly(arg);
   }
 
 // ㈠ 可打断的获取锁流程
 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
         failed = false;
         return;
       }
       if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
         // 在 park 过程中如果被 interrupt 会进入此
         // 这时候抛出异常, 而不会再次进入 for (;;)
         throw new InterruptedException();
       }
     }
   } finally {
     if (failed)
       cancelAcquire(node);
     }
   }
}

 

5.公平锁(AQS state 为0时:非公平-直接CAS竞争,公平-先检查队列,没人排队或自己在最前面才去CAS竞争 )

非公平锁实现

如果 AQS 状态为0(无锁),就直接尝试用 CAS 获取锁,这里体现了非公平性,不去检查之前已经在排队的 AQS 队列

// ㈡ 进入 ㈢
 protected final boolean tryAcquire(int acquires) {
   return nonfairTryAcquire(acquires);
 }
 
 // ㈢ Sync 继承过来的方法, 方便阅读, 放在此处
 final boolean nonfairTryAcquire(int acquires) {
   final Thread current = Thread.currentThread();
   int c = getState();
   // 如果还没有获得锁
   if (c == 0) {
     // 直接尝试用 cas 获得, 这里体现了非公平性: 不去检查之前已经在排队的 AQS 队列
     if (compareAndSetState(0, acquires)) {
       setExclusiveOwnerThread(current);
       return true;
     }
   }
   // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
   else if (current == getExclusiveOwnerThread()) {
     // state++
     int nextc = c + acquires;
     if (nextc < 0) // overflow
       throw new Error("Maximum lock count exceeded");
     setState(nextc);
     return true;
   }
   // 获取失败, 回到调用处
   return false;
 }

公平锁实现

如果 AQS 状态为 0(无锁),不会先直接尝试用 CAS 获取锁,而时先检查之前已经在排队的 AQS 队列,如果没有人排队,或者自己就在最前面,才会尝试用 CAS 竞争锁

static final class FairSync extends Sync {
   private static final long serialVersionUID = -3000897897090466540L;
   
  final void lock() {     acquire(1);   }   // AQS 继承过来的方法, 方便阅读, 放在此处   public final void acquire(int arg) {     if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {       selfInterrupt();     }   }
  
// 与非公平锁主要区别在于 tryAcquire 方法的实现   protected final boolean tryAcquire(int acquires) {     final Thread current = Thread.currentThread();     int c = getState();     if (c == 0) {       // 先检查 AQS 队列中是否有前驱节点, 没有才去竞争       if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {         setExclusiveOwnerThread(current);         return true;       }     }     else if (current == getExclusiveOwnerThread()) {       int nextc = c + acquires;       if (nextc < 0)         throw new Error("Maximum lock count exceeded");       setState(nextc);       return true;     }     return false;    }   // ㈠ AQS 继承过来的方法, 方便阅读, 放在此处   public final boolean hasQueuedPredecessors() {     Node t = tail;     Node h = head;     Node s;     // h != t 时表示队列中有 Node     return h != t &&(       // (s = h.next) == null 表示队列中还有没有老二       (s = h.next) == null ||       // 或者队列中老二线程不是此线程       s.thread != Thread.currentThread()     );   } }

 

6.条件变量

每个条件变量其实就对应着一个等待队列(锁是一个公共的等待队列 等待获得锁调用 condition.await(); 会到这个 condition 上去等待 signal

其实现类是 ConditionObject

与 sychronized 配合的 wait() 类似,使用要点:

  • await 前需要获得 ReentrantLock 锁
  • await 执行后,会释放锁,进入 conditionObject 等待
  • await 的线程被 .signal() .signalAll() 唤醒(或打断、或超时)时重新竞争 lock 锁
  • 竞争 lock 锁成功后,从 await 后继续执行

await 流程

  1. 添加当前线程节点到 condition 等待队列
  2. 释放当前线程持有的锁(锁重入计数清零)
  3. 当前线程 park 阻塞住,等待被 signal 唤醒并重新竞争获得锁后恢复运行
// 等待 - 直到被唤醒或打断
 public final void await() throws InterruptedException {
   if (Thread.interrupted()) {
     throw new InterruptedException();
   }
   
  // 添加一个 Node 至等待队列, 见 ㈠   Node node = addConditionWaiter();
  // 释放节点持有的锁(锁重入计数全部清零)   int savedState = fullyRelease(node);   int interruptMode = 0;
  // 如果该节点还没有转移至 AQS 队列, 阻塞   while (!isOnSyncQueue(node)) {     // 执行 wait 后,前面释放了锁。在这里本线程 park 阻塞      LockSupport.park(this);      // 如果被打断, 退出等待队列     if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)       break;      }
    
// 退出等待队列后, 还需要获得 AQS 队列的锁     if (acquireQueued(node, savedState) && interruptMode != THROW_IE)       interruptMode = REINTERRUPT;     // 所有已取消的 Node 从队列链表删除, 见 ㈡     if (node.nextWaiter != null)       unlinkCancelledWaiters();     // 应用打断模式, 见 ㈤     if (interruptMode != 0)       reportInterruptAfterWait(interruptMode); }

将当前线程加到 condition 链表尾部

    private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
       // 以当前线程新建一个节点 Node node
= new Node(Thread.currentThread(), Node.CONDITION); if (t == null) firstWaiter = node; else t.nextWaiter = node;
       // lastWaiter 是链表尾 lastWaiter
= node; return node; }

fullyRelease

释放锁:最后 ownerThread 空出来了,AQS state 也会成为 0,还会 unpark 锁等待队列中的下一个节点

 不管锁重入了几次,condition.wait() 的时候都要释放锁,要把锁重入计数清零

所以,一般 release(int) 方法传入的都是1,现在 getState() 后把重入计数都传进去,清零重入计数

final int fullyRelease(Node node) {
        boolean failed = true;
        try {
         // 获得锁重入计数
            int savedState = getState();
       // 一般 release 传入的都是1,现在传入重入计数,清零重入计数
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

release 唤醒锁等待队列中的下一个节点,去竞争锁

  public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

 

signal 流程

必须锁持有者才能调用 condition.signal() ,唤醒对应 condition 上的等待线程

// 唤醒 - 必须持有锁才能唤醒, 因此 doSignal 内无需考虑加锁
 public final void signal() {
   // 不是锁持有者调用 signal,抛出异常
   if (!isHeldExclusively())
     throw new IllegalMonitorStateException();
   Node first = firstWaiter;
   // 对 condition 等待队列中的第一个做 signal
   if (first != null)
     doSignal(first);
   }
  • 因为唤醒第一个节点,所以把第一个节点从 condition 的等待队列链表中断开
  • 要重新竞争锁,把这个节点加入到等待锁 AQS 队列队尾

transferForSignal 的返回值是从 condition 等待队列转移到 AQS 队列 成功与否。为什么会转移失败?

  • 在中途,可能会被打断,或等待超时。那么就取消去竞争锁了,这时就会失败。
  • 失败了,即 !transferForSignal(first) 条件就会成立,就会继续下一次循环,从 condition 等待队列中取下一个节点唤醒(first = firstWaiter)
private void doSignal(Node first) {
 do {
   // 已经是尾节点了
   if ( (firstWaiter = first.nextWaiter) == null) {
     lastWaiter = null;
   }
   // 唤醒 condition 等待队列中的第一个节点,把它从这个队列中断开
   first.nextWaiter = null;
 } while (
   // 将等待队列中的 Node 转移至 AQS 队列,成功则不再循环。不成功且还有节点则继续循环 ㈢
   !transferForSignal(first) &&
   // 队列还有节点
   (first = firstWaiter) != null
 );
 }
  1. condition 等待队列中,节点状态初始都是 Node.CONDITION。先 CAS 修改节点状态为 0(锁等待队列中最后一个节点的状态是0),前值是 Node.CONDITION,如果当前已经不是 Node.CONDITION 了,会 CAS 失败返回 false。外层会从 condition 队列中再取下一个节点唤醒
  2. enq(node) 方法将节点加入 AQS 队列,即锁等待队列的尾部。这个方法会返回前驱节点。
  3. 锁等待队列的节点状态一般是:-1 -1 -1 0,除了最后一个节点是0,其它全部是 -1。要把返回的前驱节点,即之前的最后一个节点,的状态 CAS 改为 NODE.signal() ,即 -1,表示它有责任唤醒它后面这个刚刚加入的节点
  4. unpark 唤醒的这个线程,让它可以恢复运行重新竞争锁
// 外部类方法, 方便阅读, 放在此处
 // ㈢ 如果节点状态是取消, 返回 false 表示转移失败, 否则转移成功
 final boolean transferForSignal(Node node) {
   // condition 等待队列中,节点初始状态就是 Node.CONDITION
   // 如果状态已经不是 Node.CONDITION, 说明被取消了
   if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
    // 返回 false,外层会继续唤醒 condition 等待队列中的下一个节点
     return false;
   // 加入 AQS 队列,即锁等待队列 的尾部
   // 返回的值是它的前驱节点,即之前的队列尾部,状态是初始0: -1 -1 -1 0
   Node p = enq(node);
   int ws = p.waitStatus;
   if (
     // 上一个节点被取消
     ws > 0 ||
     // 上一个节点不能设置状态为 Node.SIGNAL(-1,表示有责任唤醒下个节点)
     !compareAndSetWaitStatus(p, ws, Node.SIGNAL) 
   ) {
     // unpark 取消阻塞, 让线程重新同步状态
     LockSupport.unpark(node.thread);
   }
   return true;
 }