ReentrantLock底层原理以及AQS源代码解析

使用ReentrantLock进行同步

ReentrantLock lock = new ReentrantLock(false);//创建lock实例,false为非公平锁,true为公平锁 
lock.lock() //加锁 
lock.unlock() //解锁

ReentrantLock是一种类似synchronized的互斥锁;需要手动加锁与解锁(显式锁);支持公平锁与非公平锁;可重入。 基于AQS框架的应用实现,通过定义内部类Sync来实现AbstractQueuedSynchronizer(AQS)。

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {...}
  	...
    static final class NonfairSync extends Sync {...}
      
    static final class fairSync extends Sync {...}
    ...    
}  	

AQS

继承关系

AbstractQueuedSynchronizer继承关系如下,除了Lock,Java.concurrent.util当中还有很多并发类都是基于AQS框架实现:

image-20211207140018092

在ReentrantLock内部,还有两个子类继承了Sync,分别是NonfairSync、fairSync,在这两个类中实现了公平锁与非公平锁的逻辑。

核心属性

AbstractOwnableSynchronizer

public abstract class AbstractOwnableSynchronizer implements java.io.Serializable {
    ...
    //记录当前获取锁的线程。AbstractQueuedSynchronizer实现了该抽象类,所以也有该属性。  
    private transient Thread exclusiveOwnerThread;
  	...

AbstractQueuedSynchronizer

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
		...
    //AQS内部基于Node会构建一条双向链表(prev、next),也就是同步等待队列(CLH队列),存储等待中的线程 
    static final class Node {
      	//Node两种模式:共享 / 独占
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
				//Node四种信号量,初始状态为0
        static final int CANCELLED =  1;//出现异常,需要废弃结束
        static final int SIGNAL    = -1;//可被唤醒
        static final int CONDITION = -2;//条件等待
        static final int PROPAGATE = -3;//传播
        //初始为0  
        volatile int waitStatus;   
      	//双向链表(prev、next)
        volatile Node prev;
        volatile Node next;
      	//Node保持对线程的引用
        volatile Thread thread;   
      	...
    }
		//指向同步等待队列的头节点,初始为null
    private transient volatile Node head;
		//指向同步等待队列的尾节点,初始为null
    private transient volatile Node tail;
		//同步器状态,AQS是一个依赖状态(state)的同步器。默认为0表示当前没有线程持有锁,
    private volatile int state;

模型图示

AQS核心属性

ReentrantLock执行逻辑

实例化

ReentrantLock有两个构造函数:

//1.有参构造函数,false为非公平锁,true为公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
//2.无参构造函数,非公平锁
public ReentrantLock() {
  	sync = new NonfairSync();
}

加锁

lock

当使用lock.lock()去获取锁的时候,代码执行逻辑:

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

//ReentrantLock.Sync
//抽象方法,有两个实现分别是NonfairSync、FairSync
abstract void lock();

//ReentrantLock.FairSync
final void lock() {
  	acquire(1);
}

acquire

  • tryAcquire,竞争锁的行为,由子类实现
  • addWaiter,竞争锁失败的线程进入同步等待队列;acquireQueued,对成功入队的线程进行阻塞
//AbstractQueuedSynchronizer
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
      //acquireQueued返回true说明线程被中断,但是中断标记被清除了
      //线程自我中断,打上中断标记,为了能让外部感知到线程中断
      selfInterrupt();
}
*tryAcquire

竞争锁,公平锁实现逻辑:

  • 获取当前同步器的状态state并判断是否为0
    • state = 0可以加锁,state通过CAS修改为1
    • state != 0且当前持有线程是自己,state再+1
    • state != 0且当前持有线程不是自己,竞争锁失败
//ReentrantLock.FairSync
protected final boolean tryAcquire(int acquires) {
  	//获取当前线程
    final Thread current = Thread.currentThread();
  	//获取当前同步器的状态,并判断是否为0
    int c = getState();
  	//state = 0,当前同步器还没有被任何线程持有,可以加锁
  	//hasQueuedPredecessors判断前面是否有线程排队(公平锁)
  	//compareAndSetState通过CAS获取锁,修改state为1
  	//setExclusiveOwnerThread获取到锁之后设置自己为当前持有线程
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
          setExclusiveOwnerThread(current);
          return true;
        }
    }
  	//state != 0,表示当前已有线程持有锁,判断当前持有线程是不是自己
  	//如果是自己,state再+1。因为该分支永远只会有一个线程进入,所以不需要CAS修改state
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
          throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
  	//state != 0,而且当前持有线程不是自己,那么加锁失败
    return false;
}
*addWaiter

对竞争锁失败的线程进行入队操作:

  • 创建节点node,引用当前需要入队线程,模式为独占EXCLUSIVE;
  • 如果还没有队列,就先初始化队列,将头尾节点指向空节点;
  • 如果有队列,就将node通过CAS的方式插入到尾节点,整个过程会不断循环直到插入队列成功。
//AbstractQueuedSynchronizer
private Node addWaiter(Node mode) {
  	//传入当前线程的引用作为Node的构造参数
  	//模式是Node.EXCLUSIVE,独占
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
          pred.next = node;
          return node;
        }
    }
  	//入队
    enq(node);
    return node;
}

//AbstractQueuedSynchronizer
private Node enq(final Node node) {
  	//自旋,使入队失败的线程不断重试保证入队成功
    for (;;) {
        Node t = tail;
      	//初始化队列
        if (t == null) {
            if (compareAndSetHead(new Node()))
              //将头尾节点设置为空节点
              tail = head;
        //原子插入队列,将当前节点设置为尾节点
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
              t.next = node;
              return t;
            }
        }
    }
}


线程入队示意图:

线程入队
*acquireQueued

对入队的线程再次尝试获取锁或进行阻塞:

  • 先再次尝试获取锁,尽可能避免阻塞线程;
    • 再次获取成功,出队操作;
      • 将当前节点设置为空节点并设置为头节点;
      • 原头节点不再被引用被GC回收;
    • 再次获取失败,则对线程进行阻塞操作;
      • 把头节点状态改为-1,表示当前线程可唤醒;
      • 将当前线程阻塞;
    //AbstractQueuedSynchronizer
    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)) {
                  //获取成功,出队,将当前节点的thread引用和prev置为null,并设置当前节点为头节点
                  setHead(node);
                  //将原头节点的next置为null,此时该节点没有被引用,会被GC回收
                  p.next = null; 
                  failed = false;
                  return interrupted;
              }
              //再次获取失败,对线程进行阻塞操作
              if (shouldParkAfterFailedAcquire(p, node) &&
                  parkAndCheckInterrupt())
                	//标记线程被中断
                  interrupted = true;
            }
        } finally {
            if (failed)
              cancelAcquire(node);
        }
    }

		//AbstractQueuedSynchronizer
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
      	//获取前置节点pred的状态
        int ws = pred.waitStatus;
      	//如果前置节点pred状态为SIGNAL(-1),那么当前节点node可以被唤醒
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
          	//把前置节点pred的状态改为SIGNAL(-1),表示可以唤醒当前节点node
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

		//AbstractQueuedSynchronizer
    private final boolean parkAndCheckInterrupt() {
      	//通过LockSupport阻塞线程,直到被唤醒(两种方式:1.手动unpark();2.线程中断interrupt())
        LockSupport.park(this);
      	//返回线程中断状态,并清除掉线程中断信号
      	//如果是中断唤醒,这里会返回true
        return Thread.interrupted();
    }

线程出队示意图:

线程出队

线程阻塞示意图:

线程阻塞 (1)

释放锁

unlock

当使用lock.unlock()去释放锁的时候,代码执行逻辑:

  • tryRelease方法,释放锁;
  • unparkSuccessor方法,唤醒线程,需先判断h.waitStatus != 0,否则无法唤醒;
    //ReentrantLock
    public void unlock() {
        sync.release(1);
    }

		//AbstractQueuedSynchronizer
    public final boolean release(int arg) {
      	//释放锁成功 
        if (tryRelease(arg)) {
            Node h = head;
          	//判断头节点不为null且状态不为0,状态为0则无法唤醒
            if (h != null && h.waitStatus != 0)
              	//唤醒线程
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

*tryRelease

释放锁操作:

  • 每释放一次锁就将state-1,直到state=0;
  • 当前持有锁的线程引用置为null;
//ReentrantLock.Sync
protected final boolean tryRelease(int releases) {
  	//状态state-1,直到减为0
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
      	throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
      	free = true;
      	//当前持有锁的线程设置为null
      	setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

*unparkSuccessor

唤醒线程操作:

  • 头节点状态修改为0;
  • 唤醒线程;
//AbstractQueuedSynchronizer
private void unparkSuccessor(Node node) {
  	//获取头节点状态
    int ws = node.waitStatus;
    if (ws < 0)
      	//头节点状态改回0
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    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);
}

线程唤醒示意图:

唤醒线程

当线程被唤醒之后,会在acquireQueued方法中再次开启一轮循环,重新tryAcquire尝试获取锁。

  • 如果是在公平锁的情况下,当前node作为最前面的节点一定会获取到锁,因为没人和他竞争;
  • 但如果是非公平锁的情况下,如果此时又来了一个新的线程去竞争锁(可能会成功),那么就不能保证当前node引用的线程一定能获取成功,如果不成功就会再次走shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()逻辑,重新执行两轮循环(线程阻塞示意图)修改头节点状态为-1并阻塞当前线程。

waitStatus状态变化

CANCELLED = 1; SIGNAL = -1; CONDITION = -2; PROPAGATE = -3; 初始值 = 0;

  1. waitStatus初始值为0;
  2. 当要阻塞线程时shouldParkAfterFailedAcquire会把头节点的waitStatus修改为-1(0 —> -1),然后parkAndCheckInterrupt阻塞线程,是因为在释放锁的时候,release会判断h.waitStatus != 0,状态为0则无法唤醒;
  3. 在释放锁的时候会唤醒线程unparkSuccessor,会把头节点的waitStatus改回初始值0(-1 —> 0);
  4. 线程唤醒之后会再一次tryAcquire尝试获取锁,如果获取失败(非公平锁情况)还会重新走2的逻辑,修改头节点的waitStatus为-1(0 —> -1)并阻塞;

lockInterruptibly

ReentrantLock除了有lock方法加锁,还有lockInterruptibly也可以加锁,他们的区别在于:

  • lock方法检测到线程中断依然会继续获取锁,直到获取锁成功后才会把当前线程打上中断标记;
  • lockInterruptibly方法遇到线程中断时,会抛出InterruptedException直接返回,不会再获取锁。

lockInterruptibly源代码:

    //ReentrantLock
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

		//AbstractQueuedSynchronizer
    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
      	//如果线程中断则抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }  
    

doAcquireInterruptibly执行逻辑与acquireQueued的逻辑很类似,不同点在于if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())逻辑,acquireQueued会标记interrupted = true;而doAcquireInterruptibly是直接抛异常InterruptedException。

	//AbstractQueuedSynchronizer
  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; 
                  failed = false;
                  return;
              }
              if (shouldParkAfterFailedAcquire(p, node) &&
                  parkAndCheckInterrupt())
                	//抛异常,先进入finally代码块,failed = true
                  throw new InterruptedException();
          }
      } finally {
          if (failed)
            	//标记当前节点状态为CANCELLED =  1,出现异常,需要废弃结束节点
              cancelAcquire(node);
      }
  }

cancelAcquire

废弃节点的处理逻辑,总体来说就两部分:

  • 将节点状态置为CANCELLED=1;
  • 将节点所有的引用以及被引用(prev、next、thread等)都置为null,以便节点被GC回收;
private void cancelAcquire(Node node) {
    if (node == null)
        return;
		//将节点引用线程置为null
    node.thread = null;
		//找出所有需要废弃的cancel节点
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    Node predNext = pred.next;
		//将节点状态置为CANCELLED
    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 ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        //如果当前节点是头节点的处理逻辑  
        } else {
            unparkSuccessor(node);
        }

        node.next = node; 
    }
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值