ReentrantLock源码分析

了解前提知识:
ReentrantLock是基于AQS实现的独占锁,即同一时刻只能被一个线程占用;此外还有共享锁,例:CountDownLatch和Semaphore等。
核心状态位:在AQS中维护了一个volatile修饰的变量state,用来标记锁的状态
在这里插入图片描述
AQS实现独占锁实在内部类NODE中定义的,源码如下所示

static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

        static final int PROPAGATE = -3;

        volatile int waitStatus;

        volatile Node prev;

        volatile Node next;

        volatile Thread thread;

     
        Node nextWaiter;

        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) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

通过代码得知该NODE是一个双向列表,链表每个节点都存在上节点prev和下节点next,每个节点都保存了当前状态waitStatus和当前线程Thread,在node中通过SHARED和EXCLUSIVE表示共享和独占

static final Node SHARED = new Node();
 static final Node EXCLUSIVE = null;

NODE类中定义了4个常量

/** 表示当前节点的线程被取消 */
static final int CANCELLED =  1;
/** 表示后继节点中的线程处于等待状态,需要被唤醒 */
static final int SIGNAL    = -1;
/** 表示当前节点中的线程在等待某个条件,也就是当前节点处于condition队列中*/
static final int CONDITION = -2;
/**表示当前场景下能够执行后续的acquireShared操作*/
static final int PROPAGATE = -3;

开始源码解析:
ReentrantLock 可以实现公平锁和非公平锁,他默认实现是非公平锁,ReentrantLock 中公平锁和非公平的体现在哪呢,我们等下来说,现在开始研究非公平锁,这个会了,公平锁也就会了。

//创建一个ReentrantLock 
ReentrantLock lock = new ReentrantLock();
//点击进去看构造方法  见名知意
public ReentrantLock() {
    sync = new NonfairSync();
}
//也可以通过传参决定是不是公平锁了
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

先来看一下怎么使用。接下来,我们以这段代码作为研究对象,开始深入解析源码实现的过程

public static void main(String[] args) {
    ReentrantLock lock = new ReentrantLock();
    System.out.println(Thread.currentThread().getName()+"开始获取锁");
    lock.lock();
    System.out.println(Thread.currentThread().getName()+"获取锁成功");
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"开始获取锁");
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"获取锁成功");
        }
    },"t1").start();
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"开始获取锁");
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"获取锁成功");
        }
    },"t2").start();
}
}

在这里插入图片描述

首先看一下类图,最终实现是基于我们所说的aqs
在这里插入图片描述

1、lock()方法

点击找到lock的实现方法,第一次进来的时候通过CAS将state从0改为1,如果修改成功那么则用一个变量记录当前线程setExclusiveOwnerThread(Thread.currentThread()); 点进去就看见了exclusiveOwnerThread = thread;,我们的main线程肯定会走这个if的,t1,t2则会进入到else里面。修改成功说明当前线程获取到锁了,其他线程则会进入阻塞等待中,我们主要就来研究被阻塞的t1和t2。

final void lock() {
   if (compareAndSetState(0, 1))
       setExclusiveOwnerThread(Thread.currentThread());
   else
       acquire(1);
}

2、acquire(1);

acquire(1)的实现如下。这里可以再将该方法拆解成三步

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

2.1、tryAcquire(arg)

找到tryAcquire(arg)的实现源码,state值被main线程修改为1了 所以第一个判断不会进来了,在上面修改成功之后有这样一步处理setExclusiveOwnerThread(Thread.currentThread()); 所以这个 getExclusiveOwnerThread()==main 所以也不会进入判断,因此这个方法的代码最终结果返回false,!tryAcquire(arg)==true 程序继续执行。

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

2.2、addWaiter(Node.EXCLUSIVE)

我们要先有这样一个概念,在aqs当中会有一个双向链表来存放阻塞中的线程,在这个双线链表中,有头节点和尾结点,也就是Node prev;Node next;都是默认为null的。再继续看下面的代码。代码中static final Node EXCLUSIVE = null;也就是创建了一个空节点。将node节点中的Thread设置为当前线程,当t1先过来的时候,由于头结点和尾结点都是null 所以会走 enq(node);

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

重点来看一下 enq(final Node node)方法, 这里是个循环。也就是t1会进来,进行链表的一些初始化。
第一次循环的时候tail肯定为null所以会new Node() 并将该对象赋值给头结点和尾结点,接着进行下一次循环,第二次循环的时候由于尾结点不为null了,所以直接将传入进来的node节点的前节点设置为旧尾节点,然后将node节点修改为尾结点。这段代码的逻辑就是初始化链表,在链表上追加数据,将node节点放在最后面,修改尾结点指向。再回到上面代码,当t2过来的时候由于Node pred = tail !=null了,所以可以直接将该node节点放入到链表尾部。当这些代码执行完毕之后返回node节点。Node addWaiter(Node mode)的返回结果就是当前链表的尾结点

private Node enq(final Node node) {
     for (;;) {
         Node t = tail;
         if (t == null) { // Must initialize
             if (compareAndSetHead(new Node()))
                 tail = head;
         } else {
             node.prev = t;
             if (compareAndSetTail(t, node)) {
                 t.next = node;
                 return t;
             }
         }
     }
 }

2.3、 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

点进去看看源码先.上面说了,最后会返回尾部节点,这里的参数也就是尾部节点

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; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

addWaiter(Node mode)方法中已经记录了prev节点数据了,也就是代表尾结点的上一个节点。final Node p = node.predecessor();这个代码就是获取prev节点

final Node predecessor() throws NullPointerException {
    Node p = prev;
    if (p == null)
        throw new NullPointerException();
    else
        return p;
}

代码执行继续if (p == head && tryAcquire(arg)) 首先看一下是不是头结点,如果是头结点的话,再次尝试获取,如果执行失败,会执行到 if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())这一块代码,这块代码才是真正阻塞的代码,执行成功,则将头结点设置为当前node节点。

2.3.1、shouldParkAfterFailedAcquire(p, node)

这名字起的多好,按照我的翻译是:获取失败后应该暂停。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } 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;
}

获取p节点的waitStatus,默认为0,这里不等于Node.SIGNAL 所以就会进入else,修改p节点的waitStatus=Node.SIGNAL=-1,返回false之后继续重新进入for循环,直到返回true

2.3.2、parkAndCheckInterrupt()

上面方法返回true之后,则会进入parkAndCheckInterrupt()方法中,该方法就是让当前线程阻塞到这一步,线程会执行到这一步暂停,直到被唤醒

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

3、unlock()

unlock的实现如下

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

3.1、tryRelease(arg)

这段代码很好理解

protected final boolean tryRelease(int releases) {
	// state= state-1,
    int c = getState() - releases;
    //如果当前线程不是拥有锁的线程,直接抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    /**这里在判断一下,可能某个资源多次加锁了
    假如main线程调用两次lock方法,但是却只调用了一次unlock方法的话,也是不能成功释放锁资源的
    如果state=0了那么则返回true  setExclusiveOwnerThread(null);
    */
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

3.2unparkSuccessor(h)

判断一下if (h != null && h.waitStatus != 0)后调用
h!=null是为了防止队列为空,即没有任何线程处于等待队列中,那么也就不需要进行唤醒的操作
h.waitStatus != 0是为了防止队列中虽有线程,但该线程还未阻塞,由前面的分析知,线程在阻塞自己前必须设置前驱结点的状态为SIGNAL,否则它不会阻塞自己。我们这里可以发现每次唤醒的时候只会唤醒头结点的下一节点。

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    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);
}

这里去唤醒阻塞线程,被阻塞的线程被唤醒之后,就会从之前被阻塞的地方继续执行,这里是上次阻塞的地方。代码从这里继续执行后,被唤醒的继续获取锁,获取成功后修改头结点,最后返回,完成调用

private final boolean parkAndCheckInterrupt() {
   LockSupport.park(this);//上次阻塞的代码
   return Thread.interrupted();
}

最后公平锁和非非公平锁的体现

1、获取锁的时候非公平锁多一步尝试获取锁
在这里插入图片描述
2、
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值