ReentrantLock原理与源码介绍

一、AQS原理

同步状态State+ 同步等待队列
在这里插入图片描述

如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态(cas状态);如果共享资源被占用,就需要一定的阻塞等待唤醒机制(LockSupport.park跟unpark)来保证锁分配。

private volatile int state; #临界状态
#用CAS的原子性操作state
protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    
private transient Thread exclusiveOwnerThread;//代表抢占到锁的线程

#核心内部类 -组成双向同步队列
static final class Node {
volatile Node next;//后一个节点
volatile Node prev;//前一个节点
volatile Thread thread;
volatile int waitStatus;//线程内部状态
}
private transient volatile Node head;//同步等待队列头
private transient volatile Node tail; //同步等待队列尾

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&  //尝试获取锁。tryAcquire为抽象方法,由子类方法实现
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))   #如果获取锁失败,addWaiter把该线程加入到同步队列。acquireQueued调用park方法阻塞自己
        selfInterrupt();
}

acquire()包含三个重要的方法。tryAcquire()由子类实现,为模板设计模式。addWaiter()把当前线程封装成一个节点,并加入到等待队列中。
acquireQueued()不断尝试获取锁,若获取失败,则调用park阻塞自己,直到获取到锁。

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)) {//如果队列非空,把node节点加到队列尾部 
            pred.next = node;
            return node;
        }
    }
    enq(node);//如果队列头是空的,则先创建队一个哨兵节点作为队列头,再把node节点放到队列尾部
    return node;
}

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()) #调用park方法阻塞自己 
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

public final boolean release(int arg) {
    if (tryRelease(arg)) { //尝试释放锁。释放成功,继续往下走
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//调用unpark方法唤醒同步队列中的第一个等待线程
        return true;
    }
    return false;
}


private void unparkSuccessor(Node node) {

    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);//调用unpark唤醒队列头的线程
}

二、ReentrantLock源码

在这里插入图片描述
1.如图,为ReentrantLock与AQS的关系。ReentrantLock有一个Sync属性,而Sync最终继承自AQS,可以理解为ReentrantLock含有一个AQS属性。
2.ReentrantLock里面有一个Sync变量。lock()方法最终调用的都是Sync#lock()。而Sync#lock()方法最终调用的AQS#acquire()方法。
3.同时要注意,AQS#acquire()方法暴露一个抽象方法tryAcquire()由子类具体实现。AQS实现了获取锁失败后让线程加载到队列中阻塞等待,释放锁后唤醒同步队列中的线程等。而如何去获取锁由子类去实现,即由ReentrantLock内部类FairSync、NonfairSync来实现。此为典型的模板模式。

非公平锁的尝试抢锁代码:

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {//如果临界状态state=0,非公平锁不管有没有线程在同步队列等待,先尝试用CAS跟新state状态
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//如果占用线程是当前线程,获取锁成功,跟新state值
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

在这里插入图片描述
1.通过ReentrantLock的加锁方法Lock进行加锁操作,会调用到内部类Sync的Lock方法
2.非公平锁会尝试用CAS去抢占临界状态获取锁。如果抢占成功则获取锁成功,抢占失败,则调用AQS#acquire方法获取锁。而公平锁则不会尝试 用CAS去抢占临界状态,此为公平锁与非公平锁的区别一。
3.AQS#acquire会先调用tryAcquire方法。判断临界状态state是否为0。若state非0,则判断当前当前线程是否已经持有锁,
currentThread = getExclusiveOwnerThread。若已经持有锁,根据锁的可重入性,获取锁成功,并且跟新state状态(state+1);若当前线程不持有锁,则获取锁失败
4.若临界状态state=0。非公平锁会直接尝试用CAS去抢占锁,而公平锁要先判断队列中没有其他线程在等待(!hasQueuedPredecessors)才会去抢占锁。此为公平锁与非公平锁的区别二。

尝试释放锁代码:

 protected final boolean tryRelease(int releases) {
     int c = getState() - releases;
     if (Thread.currentThread() != getExclusiveOwnerThread())
         throw new IllegalMonitorStateException();
     boolean free = false;
     if (c == 0) {
         free = true;
         setExclusiveOwnerThread(null);
     }
     setState(c);
     return free;
 }

在这里插入图片描述
unparkSuccessor(head) 把队列头的线程唤醒,这也体现了之前维护等待队列的一个重要作用。
1.通过ReentrantLock的释放锁方法unlock进行解锁操作,会调用到AQS的release方法
2.release会调用子类Sync#tryRelease方法释放锁,此时会先判断该线程持有锁,否则会抛出IllegalMonitorStateException异常。
3.如果state-release=0,则释放锁成功,把exclusiveOwnerThread设置为null,并且调用unparkSuccessor把等待队列中的头节点唤醒;
如果state-release!=0,则该锁为重入锁,释放锁失败。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值