[java 并发] ReentrantLock源码详细解读

首先弄明白它的内部类之间的关系

Reentrantlock 内部类:

Sync ->AbstractQueuedSynchronizer

NonfairSync -> Sync

FairSync->Sync

AbstractQueuedSychronizer 内部类:Node

reentrantlock中实现公平锁和非公平锁

	// 内部类 Sync的一个引用
	private final Sync sync;

	// 默认构造器为实现非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }
	// 根据传参选择公平锁还是非公平锁
	public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }	

上述中出现的三个类的定义,具体内容接下来讲

// 实现同步机制的一个父类,继承了AbstractQueuedSynchronizer 下面有两个子类
abstract static class Sync extends AbstractQueuedSynchronizer 
// 公平锁子类
static final class FairSync extends Sync
// 非公平锁子类
static final class NondairSync extends Sync

lock方法

实现独占锁的状况有三种

  1. 当前锁的状态为0 ,那么当前线程获取锁并将锁状态改为1,执行线程的业务逻辑
  2. 当前线程已经获得该锁,将锁状态+1,执行业务逻辑
  3. 锁状态不为0,当前线程也没有获得锁,该线程阻塞,等待获得锁后被唤醒,锁状态改为1,执行逻辑
    public void lock() {
        // 调用公平锁或非公平锁的lock方法
        sync.lock();
    }

lock实现的是sync中的抽象方法,该抽象方法在子类中的实现有两个,分别在公平类和非公平类中

非公平锁:

final void lock() {
    // 如果cas尝试获取锁成功(将state锁状态从0设置为1)
   if (compareAndSetState(0, 1))
       //设置当前线程为独占锁的线程
      setExclusiveOwnerThread(Thread.currentThread());
   else
   // 调用AbstractQueuedSynchronizer类中的acquire方法(该方法的具体实现)
      acquire(1);
}
  1. AbstractQueuedSynchronizer 这个类中关于锁状态值的内容如下
// 定义锁状态值
private volatile int state;
// 获取锁状态方法
protected final int getState() {
    return state;
}
// 更改锁状态值的方法
protected final void setState(int newState) {
    state = newState;
}
  1. AbstractOwnableSynchronizer类中 setExclusiveOwnerThread方法
// 当前拥有独占锁的线程
private transient Thread exclusiveOwnerThread;
// 设置独占锁的线程为传入的线程
protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}
  1. acquire方法
// 调用的是AQS中的这个方法
public final void acquire(int arg) {
    //如果获取失败返回false 会继续执行将当前线程链入队尾并挂起
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();//中断自己
}
  1. 1 tryAcquire 方法(该方法有两个子类来实现,分别是FairSync,NonfairSync)

    非公平类中方法如下

  // NonfairSync 类中
   protected final boolean tryAcquire(int acquires) {
       // 其父类Sync中的方法
       return nonfairTryAcquire(acquires);
   }
   
   // Sync类中
   final boolean nonfairTryAcquire(int acquires) {
       final Thread current = Thread.currentThread();
       //获取锁状态值
       int c = getState();
       if (c == 0) {
           //如果当前没有线程在使用,直接使用cas尝试获取锁,新的线程可能抢占已经排队的线程的锁的使用权,
           if (compareAndSetState(0, acquires)) {
               setExclusiveOwnerThread(current);// 设置当前线程独占锁
               return true;
           }
       }
       //如果不为 0 则判断当前线程是不是独占锁的线程
       else if (current == getExclusiveOwnerThread()) {
           // 如果是将锁数量状态值+1(可重入锁来源)
           int nextc = c + acquires;
           if (nextc < 0) // overflow
               throw new Error("Maximum lock count exceeded");
           setState(nextc);
           return true;
       }
       //请求即没有获取到锁,也不是当前独占锁的线程,返回false
       return false;
   }

3.2 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法 - 将当前线程链入队尾并挂起,等待被唤醒

3.2.1 AQS 中的 Node 类(这里是Node类实现的全部内容,有些用于共享锁,有些用于独占锁)

	/**
        * 同步等待队列(双向链表)中的节点
        */
       static final class Node {
           // 线程取消标识
           static final int CANCELLED = 1;
           /** 
            * 如果前驱节点的等待状态是SIGNAL,表示当前节点将来可以被唤醒,那么当前节点就可以安全的挂起了 
            * 否则,当前节点不能挂起 
            */
           static final int SIGNAL = -1;
           // 线程正在等待条件
           static final int CONDITION = -2;
   		// waitStatus值,指示下一个acquireShared应该无条件传播(共享锁使用)
           static final int PROPAGATE = -3;
           // 指示节点正在共享模式下等待的标记(共享锁使用)
           static final Node SHARED = new Node();
           // 一个标记:用于表明该节点正在独占锁模式下进行等待
           static final Node EXCLUSIVE = null;
           // 值就是前四个int(CANCELLED/SIGNAL/CONDITION/PROPAGATE),再加一个0
   
           volatile int waitStatus;
           //前驱节点
           volatile Node prev;
   
           // 后继节点 
           volatile Node next;
   
           // 节点中的线程 
           volatile Thread thread;
   		// 链接到等待条件的下一个节点,或特殊值 SHARED。(共享锁使用)
           Node nextWaiter;
   
           // 如果节点在共享模式下等待,返回 true (共享锁使用)
           final boolean isShared() {
               return nextWaiter == SHARED;
           }
   
           // 返回该节点前一个节点
           final Node predecessor() throws NullPointerException {
               Node p = prev;
               if (p == null)
                   throw new NullPointerException();
               else
                   return p;
           }
   
           Node() { // Used to establish initial head or SHARED marker
           }
   
           Node(Thread thread, Node mode) { // 用于 addWaiter中
               this.nextWaiter = mode;
               this.thread = thread;
           }
   
           Node(Thread thread, int waitStatus) { // Use by Condition
               this.waitStatus = waitStatus;
               this.thread = thread;
           }
       }

3.2.2 addWaiter(Node mode) 将node添加进等待队列

  static final Node EXCLUSIVE = null;
   addWaiter(Node.EXCLUSIVE)
       
   /** 将node节点加入等待队列
     * 快速入队,入队成功返回node
     * 入队失败采用正常入队
     * return 返回当前要插入的这个节点
     */
   private Node addWaiter(Node mode) {
   	    // 创建节点
           Node node = new Node(Thread.currentThread(), mode);
           // Try the fast path of enq; backup to full enq on failure
       	// 快速入队,将尾节点赋给pred
           Node pred = tail;
   	    // 尾节点不为空
           if (pred != null) {
               // 将尾节点作为创造出来的节点的前一个节点,将node连接到尾节点后
               node.prev = pred;
               // 基于cas将node设置为尾节点,如果失败说明当前线程获取尾节点时有其他线程将尾节点替换过
                /**
                * 注意:假设有链表node1-->node2-->pred(当然是双链表,这里画成双链表才合适),
                * 通过CAS将pred替换成了node节点,即当下的链表为node1-->node2-->node,
                * 然后根据上边的"node.prev = pred"与下边的"pred.next = node"将pred插入到双链表中去,组成最终的链表如下:
                * node1-->node2-->pred-->node
                * 这样的话,实际上我们发现没有指定node2.next=pred与pred.prev=node2,这是为什么呢?
                * 因为在之前这两句就早就执行好了,即node2.next和pred.prev这连个属性之前就设置好了
                */
               if (compareAndSetTail(pred, node)) {
                   pred.next = node;
                   return node;
               }
           }
           enq(node);// 正常入队
           return node;
       }

enq(node) 正常入队的方法

   /**
        * 正常入队
        * @param node
        * @return 之前的尾节点
        */
       private Node enq(final Node node) {
           for (;;) {//无限循环,一定要阻塞到入队成功为止
               Node t = tail;//获取尾节点
               if (t == null) { //如果尾节点为null,说明当前等待队列为空         
                   /*
                    * 基于CAS将新节点(一个dummy节点)设置到头上head去,如果发现内存中的当前值不是null,则说明,在这个过程中,已经有其他线程设置过了。
                    * 当成功的将这个dummy节点设置到head节点上去时,我们又将这个head节点设置给了tail节点,即head与tail都是当前这个dummy节点,
                    * 之后有新节点入队的话,就插入到该dummy之后
                    */
                   if (compareAndSetHead(new Node()))
                       tail = head;
               } else {//这里的逻辑和快速入队一样
                   node.prev = t;// 将创造出来的结点连接到尾节点后面
                   if (compareAndSetTail(t, node)) {//尝试将node节点设为尾节点
                       t.next = node;//将node节点设为尾节点
                       return t;
                   }
               }
           }
       }

3.2.3 acquireQueued()

   final boolean acquireQueued(final Node node, int arg) {
       boolean failed = true;
       try {
           boolean interrupted = false;
   		// 一直阻塞,直到node的前驱结点p之前的所有节点都执行完毕,p成为head且node请求成功
           for (;;) { 
               // 获取插入节点的前一个节点p
               final Node p = node.predecessor();
   			  /*
                    * 1、这个是跳出循环的唯一条件,除非抛异常
                    * 2、如果p == head && tryAcquire(arg)第一次循环就成功了,interrupted为false,不需要中断自己
                    * 如果p == head && tryAcquire(arg)第一次以后的循环中如果执行了挂起操作后才成功了,interrupted为true,就要中断自己
                    */
               if (p == head && tryAcquire(arg)) { 
                   setHead(node);// 当前节点设置为头结点
                   p.next = null; // help GC
                   failed = false; 
                   return interrupted;// 跳出循环
               }
               // 检测当前节点是否可以被安全挂起 ,挂起当前线程
               if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                	//中断
                   interrupted = true;
           }
       } finally {
           if (failed)
               cancelAcquire(node);
       }
   }

shouldParkAfterFailedAcquire(p,node) 检测当前节点是否可以被安全挂起

     private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
       // 获取前驱节点的等待状态
           int ws = pred.waitStatus;
           // 如果前驱节点的等待状态是signal,表示当前节点将来可以被唤醒,那么就可以安全的挂起
           if (ws == Node.SIGNAL)
               return true;
           // ws>0 (cancelled ==1 )前驱节点的线程被取消了,会将该节点之前的连续几个被取消的前驱节点从队列中剔除,返回false,不能挂起
           // ws<0 && !=SIGNAL ,将当前节点的前驱节点的等待状态设为SIGNAL
           if (ws > 0) {
               do {
                   // pred=pred.prev
                   // node.prev=pred
                   // 被取消的节点去除,
                   node.prev = pred = pred.prev;
               } while (pred.waitStatus > 0);
               // 找到 ws<0的节点和当前节点连接起来
               pred.next = node;
           } else {
              /*
                * 尝试将当前节点的前驱节点的等待状态设为SIGNAL
                * 1/这为什么用CAS,现在已经入队成功了,前驱节点就是pred,除了node外应该没有别的线程在操作这个节点了,那为什么还要用CAS?而不直接赋值呢?
                * (解释:因为pred可以自己将自己的状态改为cancel,也就是pred的状态可能同时会有两条线程(pred和node)去操作)
                * 2/既然前驱节点已经设为SIGNAL了,为什么最后还要返回false
                * (因为CAS可能会失败,这里不管失败与否,都返回false,下一次执行该方法的之后,pred的等待状态就是SIGNAL了)
                */
               compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
           }
           return false;
       }

parkAndCheckInterrupt()方法-挂起当前线程

   // 挂起当前线程   
   private final boolean parkAndCheckInterrupt() {
           LockSupport.park(this);
           return Thread.interrupted();
       }

cancelAcquire(node) 方法

     private void cancelAcquire(Node node) {
           // Ignore if node doesn't exist
           if (node == null)
               return;
   
           node.thread = null;
   
           // 跳过取消的前节点
           Node pred = node.prev;
           while (pred.waitStatus > 0)
               node.prev = pred = pred.prev;
   
           // predNext是要取消拼接的明显节点。 如果没有,以下情况将失败,在这种情况下,我们输掉了比赛,而另一个取消或发出信号,因此无需采取进一步措施。
           Node predNext = pred.next;
   
   		// 	用无条件写入代替cas,完成这步后其他节点可以跳过,在这以前不会受其他线程干扰
           node.waitStatus = Node.CANCELLED;
   
           // 如果这是尾节点,移开自己
           if (node == tail && compareAndSetTail(node, pred)) {
               compareAndSetNext(pred, predNext, null);
           } else {
   		// 如果后继者需要信号,请尝试设置pred的下一个链接
           // 所以它会得到一个。 否则唤醒它以传播。
               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; // help GC
           }
       }

公平锁

final void lock() {
    acquire(1);
}
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
	// 获取锁状态
    int c = getState(); 
    // 如果当前没有线程获取锁,
    if (c == 0) {
        // !hasQueuedPredecessors()保证线程都按顺序使用锁
        // 判断当前线程是否为等待队列中的头结点
        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;
}

判断是否为头结点

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

总结:公平锁与非公平锁对比

  • FairSync:lock()少了插队部分(即少了CAS尝试将state从0设为1,进而获得锁的过程)
  • FairSync:tryAcquire(int acquires)多了需要判断当前线程是否在等待队列首部的逻辑(实际上就是少了再次插队的过程,但是CAS获取还是有的)。

最后说一句,

  • ReentrantLock是基于AbstractQueuedSynchronizer实现的,AbstractQueuedSynchronizer可以实现独占锁也可以实现共享锁,ReentrantLock只是使用了其中的独占锁模式

unlock

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    // 如果释放锁成功
    if (tryRelease(arg)) {
        
        Node h = head;
        // 如果头节点不为空,而且节点状态不为0时
        if (h != null && h.waitStatus != 0)
            // 唤醒线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

tryRelease方法在Sync类中

protected final boolean tryRelease(int releases) {
    // 将锁状态值减去当前需要释放的值
    int c = getState() - releases;
    // 如果当前线程不是独占锁的线程,抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果当前锁状态值为0
    if (c == 0) {
        free = true;
        // 设置当前独占锁的线程为null
        setExclusiveOwnerThread(null);
    }
    // 设置锁状态为修改后的值
    setState(c);
    return free;
}

unparkSuccessor(Node node)方法

private void unparkSuccessor(Node node) {
	//  如果状态小于0 ,尝试更改状态为0
    int ws = node.waitStatus;
    if (ws < 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);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

荼白z

感谢老板请我喝咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值