> 文章列表 > ReentrantLock源码分析(一)加锁流程分析

ReentrantLock源码分析(一)加锁流程分析

ReentrantLock源码分析(一)加锁流程分析

一、ReetrantLock的使用示例

static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        new Thread(ClassLayOutTest::reentrantLockDemo, "threadA").start();
        Thread.sleep(1000);
        new Thread(ClassLayOutTest::reentrantLockDemo, "threadB").start();
        Thread.sleep(1000);
        new Thread(ClassLayOutTest::reentrantLockDemo, "threadC").start();
    }

    public static void reentrantLockDemo() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ": 获取到锁! -->" + System.currentTimeMillis() / 1000);
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

二、流程分析

上面的有三个线程都会去执行reentrantLockDemo()方法,在抢占到锁后会睡眠5S。然后在线程A启动后先睡眠1秒再启动B线程,线程B启动后睡眠1秒再启动线程C。这样就能保证了线程ABC按照固定顺序去抢占,那么程序执行的顺序是:

1、线程A抢占锁资源

线程A直接过来后,由于没有锁还没有被线程抢占。可以直接抢占到锁,线程A直接通过CAS将state的值由0修改为1,head及tail为null

2、线程B抢占失败进入AQS队列

线程B执行加锁操作的时候,由于锁已经被线程A占有了,所以没抢占到,执行进入AQS队列操作。具体的操作就是把自己封装成一个Node对象

放入到双向队列的时候,前面需要有一个伪头节点(节点中thread为null,waitStatus = -1),B放入进入后对应的node节点属性(thread = 线程B,waitStatus = 0)

3、线程B抢占失败,继续进入到AQS队列

线程C过来后,由于线程A已经抢占到锁资源,并且其业务代码需要执行5s中,那么此时线程c也无法抢到锁,需要进行进入AQS队列操作。在线程C进入AQS队列后,AQS目前现在应该有3个节点,伪节点 + Node(B) + Node(C)三个节点,状态如下所示:

最终我们看到示意图如下:

三、代码分析

1、lock方法分析

1.1、执行lock方法后,公平锁和非公平锁的执行方法不一样

// 非公平锁的实现
final void lock() {// 上来先尝试使用CAS的方法将0设置为1if (compareAndSetState(0, 1))//获取锁资源成功后,将当前线设置给属性字段exclusiveOwnerThreadsetExclusiveOwnerThread(Thread.currentThread());else// 尝试获取到1个资源acquire(1);
}

// 公平锁

final void lock() {acquire(1);
}

1.2、acquire方法分析,这里的公平锁和非公平锁的逻辑是相同的

public final void acquire(int arg) {
    // tryAcquire:再次查看,当前线程是否可以尝试获取锁资源
    if (!tryAcquire(arg) &&
        // 没有拿到锁资源
        // addWaiter(Node.EXCLUSIVE):将当前线程封装为Node节点,插入到AQS的双向链表的结尾
        // acquireQueued:查看我是否是第一个排队的节点,如果是可以再次尝试获取锁资源,如果长时间拿不到,挂起线程
        // 如果不是第一个排队的额节点,就尝试挂起线程即可
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // 中断线程的操作
        selfInterrupt();
}

1.3 、tryAcquire方法,,分为公平锁和非公平锁

// 非公平锁竞争锁资源逻辑
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();// 获取当前state属性值 int c = getState();// state等于0的时候,说明之前持有锁的线程已经释放了锁资源if (c == 0) {// 基于CAS尝试将state由0设置为1if (compareAndSetState(0, acquires)) {// 设置互斥锁的拥有者为当前线程setExclusiveOwnerThread(current);return true;}}// 如果当前线程跟之前锁的拥有者是同一个,那么此处就是锁的重入else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;// 重入成功后,需要检查是否溢出if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");// 设置statu加1的值setState(nextc);return true;}return false;
}

-------------------------------------------------------------------------------------------------------------------------

// 公平锁实现逻辑

protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 队列为空或者当前线程排在队列的第一位置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;
}

 1.4、addWaiter方法分析

在没有拿到锁资源的时候,把线程放入到AQS队列中去

private Node addWaiter(Node mode) {// 将当前的线程封装成node对象,这里mode是互斥模式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;
}