ReentrantLock源码

文章目录

      • UML图
      • 源码分析
        • 加锁 lock()
        • 释放锁 unlock()
      • 总结

UML图

ReentrantLock源码_第1张图片
ReentrantLock的底层就是由AQS来实现的

源码分析

加锁 lock()

在调用ReentrantLock中的lock()方法,内部会去调用 sync.lock(),具体实现分别在sync的子类 NoFairSync、FairLockSync中的 lock() 方法

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

//公平锁实现
final void lock() {
   acquire(1);
}


//非公平锁实现
final void lock() {
     if (compareAndSetState(0, 1))
       setExclusiveOwnerThread(Thread.currentThread());
     else
        acquire(1);
}

从代码中可以看出,公平锁和非公平锁在线程获取的时候第一个区别就是,公平锁直接调用 acquire(1) 获取锁,而非公平锁会先使用 CAS 尝试获取锁,如果获取到就将当前锁设置为独占锁,如果获取不到才调用 acquire(1) 获取锁

接着分析 acquire(1) 方法

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

acquire中有四个方法,逐个进行分析:

先看第一个方法 tryAcquire(arg),这个方法在公平锁和非公平锁都进行重写

公平锁:

protected final boolean tryAcquire(int acquires) {
    //此时 acquires=1
    //先获取当前线程
    final Thread current = Thread.currentThread();
    //获取锁状态
    int c = getState();
    //如果锁状态=0,表示当前锁没有被其他线程占有
    if (c == 0) {
        //hasQueuedPredecessors()判断双向链表中是否有线程在排队,没有线程排队(返回false),强锁
        //如果有线程排队,瞜一眼当前线程是否排在第一个,如果是(返回false),抢锁
        if (!hasQueuedPredecessors() &&
             //使用CAS尝试获取锁,获取到了,将state设置为1
             compareAndSetState(0, acquires)) {
             //设置当前线程为独占锁线程
             setExclusiveOwnerThread(current);
               return true;
          }
     }
     //如果state不为0,表示锁已经被其他线程持有,判断持有锁的线程是否是当前线程,如果是
     else if (current == getExclusiveOwnerThread()) {
        //state加上acquires
        int nextc = c + acquires;
        //防止溢出,抛出异常
        if (nextc < 0)
          throw new Error("Maximum lock count exceeded");
          //否则,设置state值
          setState(nextc);
          return true;
      }
     return false;
 }

非公平锁:

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

final boolean nonfairTryAcquire(int acquires) {
      //获取当前线程
      final Thread current = Thread.currentThread();
      //获取锁状态state
      int c = getState();
      //如果锁没有被线程持有
      if (c == 0) {
         //直接使用CAS获取锁
         if (compareAndSetState(0, acquires)) {
           //获取到锁,将当前线程设置为独占锁线程
           setExclusiveOwnerThread(current);
           //直接返回
           return true;
           }
       }
       //如果锁已经被线程持有,判断持有所线程是否为当前线程
       else if (current == getExclusiveOwnerThread()) {
       //线程状态加上acquires
       int nextc = c + acquires;
       //溢出,直接抛出异常
       if (nextc < 0) // overflow
          throw new Error("Maximum lock count exceeded");
       //设置线程状态,代表可重入成功
       setState(nextc);
       //直接返回true
       return true;
      }
      return false;
}

从公平锁和非公平锁的 tryAcquire() 代码中可以看出,当锁状态为0时,公平锁会先去看等待锁队列中是否有其他线程在等待锁,如果没有再去获取锁,如果有再看当前线程是否排在第一个,如果是再获取锁,否则无法获取锁而非公平锁直接使用CAS获取锁

再看第二个方法 addWaiter(Node.EXCLUSIVE)

private Node addWaiter(Node mode) {
        //将当前线程封装为一个双向链表节点
        Node node = new Node(Thread.currentThread(), mode);
        //获取链表尾节点
        Node pred = tail;
        // tail还未初始化时不能走这个逻辑,由于刚开始head和tail都指向null,执行enq()方法
        //如果尾节点不为空,将当前节点放在尾部
        if (pred != null) {
            //将尾结点设置为当前节点的前驱节点
            node.prev = pred;
            //使用CAS将尾结点设置为当前节点
            if (compareAndSetTail(pred, node)) {
                //如果设置成功,尾结点的后继节点连接到当前节点
                pred.next = node;
                //返回当前节点
                return node;
            }
        }
        //节点没有初始化,执行enq()
        enq(node);
        return node;
}

private Node enq(final Node node) {
    //死循环
    for (;;) {
      //获取尾结点,用t指向
      Node t = tail;
      //如果尾结点为空
      if (t == null) {
         //使用CAS初始化虚拟的头结点
         if (compareAndSetHead(new Node()))
                    //将tail也指向head
                    tail = head;
      } else {
         //   节点添加
         // 尾结点不为空,将当前节点的前驱结点指向尾结点
         node.prev = t;
         //使用CAS将当前节点设置为尾结点,此处使用临时变量t意义就是将t作为期望值,保证操作的原子性
         if (compareAndSetTail(t, node)) {
            //尾结点的后继指向当前节点
            t.next = node;
            //返回插入节点之前的尾结点,目的就是为了结束for循环
            return t;
          }
      }
    }
}

从addWaiter()方法可以看出,当 tryAcquire() 方法没有获取到锁,执行 addWaiter() 就是将当前线程封装为一个Node节点放入到一个双向链表中,如果放入之前链表未进行初始化,先进行初始化,再添加到链表中,并返回当前节点,如果链表已经初始化且链表不为空,就将当前节点放入到链表的尾部,返回当前节点

再看第三个方法 acquireQueued(final Node node, int arg),再次获取锁,如果获取锁失败,就会一直自旋,尝试获取锁,直到成功获取锁

final boolean acquireQueued(final Node node, int arg) {
    //定义一个失败的标志,true失败
    boolean failed = true;
    try {
        //定义一个中断的标志,false未中断
        boolean interrupted = false;
        //死循环
        for (;;) {
            //获取node节点的前驱结点
            final Node p = node.predecessor();
            //如果前驱结点为头结点,说明node是第一个节点,那么当前线程就去竞争锁使用tryAcquire,逻辑与前面一致
            if (p == head && tryAcquire(arg)) {
                //获取到了锁,将node的节点赋值给头结点,并将node的前驱后继置空
                setHead(node);
                p.next = null; // help GC
                //将失败标志设为false,证明成功获取到锁
                failed = false;
                //返回false
                return interrupted;
            }
            //如果当前线程不在链表头部或者获取锁失败
            //shouldParkAfterFailedAcquire 用于判断在获取锁失败后,当前线程是否应该进入等待状态
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //线程中断
                interrupted = true;
        }
    } finally {
        if (failed)
            //获取锁成功后,将该节点从等待队列中移除,从而取消线程获取锁的尝试
            cancelAcquire(node);
    }
}


final Node predecessor() throws NullPointerException {
    //获取当前节点的前驱节点
    Node p = prev;
    if (p == null)
        throw new NullPointerException();
    else
        return p;
}

//该方法主要用于在获取锁失败时进行适当的线程挂起和恢复。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //获取前驱节点的状态
    int ws = pred.waitStatus;
    //如果节点状态为 -1 ,表示该节点需要被前驱结点进行唤醒
    //表示前驱节点已经设置为唤醒当前节点的状态。这意味着当前线程应该进入等待状态(park)
    if (ws == Node.SIGNAL)
        return true;
    //节点状态为 1,表示节点已取消排队
    //表示前驱节点已经取消排队,或者出现了一些异常情况。在这种情况下,需要遍历等待队列,找到一个合适的前驱节点,并将当前节点插入到合适的位置
    if (ws > 0) {
        do {
           //将当前节点的前驱节点指向当前节点前驱节点的前驱节点
            node.prev = pred = pred.prev;
            //找到一个有效的前驱节点
        } while (pred.waitStatus > 0);
        //前驱节点的前驱节点后继节点指向当前节点
        pred.next = node;
    } else {
        //状态为 0 或 -3 或 -2 ,使用CAS将前驱节点状态改为 -1,前驱节点需要唤醒当前节点
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

//shouldParkAfterFailedAcquire返回true,说明需要将当前线程挂起,执行parkAndCheckInterrupt
//将当前线程挂起(park)并检查是否被中断。
private final boolean parkAndCheckInterrupt() {
    // 立即挂起线程,导致当前线程进入等待状态,直到被其他线程唤醒或中断。
    LockSupport.park(this);
    // 检查线程的中断状态,如果为true,清除中断状态将中断状态设置为false,返回true,否则返回false
    return Thread.interrupted();
}

从acquireQueued(final Node node, int arg)方法可以看出,获取当前节点的前驱节点,如果前驱节点为头结点,就开始调用 tryAcquire() 方法获取锁,获取成功就直接返回,如果获取失败,判断是否需要挂起当前线程,如果前驱节点waitStatus为-1,通过LockSupport.park()挂起当前线程,如果前驱节点waitStatus>0(waitStatus=1),表示前驱结点处于取消状态,移除前驱节点,否则就将前驱节点waitStatus设置为 -1,不挂起当前线程

释放锁 unlock()

在调用 ReentrantLock的 unlock()方法时,内部会调用 sync.release(1) 方法

public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    //如果返回true,表示锁已经释放,false:还未释放
    if (tryRelease(arg)) {
        //获取头结点
        Node h = head;
        //头结点不为空并且等待状态不为0
        if (h != null && h.waitStatus != 0)
            //从后往前遍历唤醒等待节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}


protected final boolean tryRelease(int releases) {
    //获取当前锁状态,将锁状态减去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;
}


private void unparkSuccessor(Node node) {
    //获取头结点等待状态
    int ws = node.waitStatus;
    //小于0
    if (ws < 0)
        //将头结点等待状态设置为0
        compareAndSetWaitStatus(node, ws, 0);
    //获取头结点下一个节点
    Node s = node.next;
    //下一个节点为null 或者 等待状态 >0
    if (s == null || s.waitStatus > 0) {
        //将后继节点设置为 null
        s = null;
        //从后往前遍历
        for (Node t = tail; t != null && t != node; t = t.prev)
            //如果遇到节点等待状态 <= 0
            if (t.waitStatus <= 0)
                //将t赋值给s
                s = t;
    }
    // 如果s不为null
    if (s != null)
        //唤醒s
        LockSupport.unpark(s.thread);
  }

总结

你可能感兴趣的:(Java,java,jvm)