ReentrantLock源码浅析

ReentrantLock 有两种实现锁的方式 一种是非公平的,一种是公平的,默认的实现方式是非公平的,但不管哪种实现方式,ReentrantLock都是依靠的它的静态内部了syc来实现,底层的实现机制都是利用volatitle语义 和操作系统提供的CAS原语来实现

所以在看本篇博文时请确保你有以上基础 ,下面上源码 (jdk1.8)

public ReentrantLock() {
    sync = new NonfairSync(); //默认的实现是非公平锁
}

静态内部类的实现
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() { //这里获取锁
        if (compareAndSetState(0, 1))  //这里是获取锁的关键
            setExclusiveOwnerThread(Thread.currentThread()); //将当前拥有锁的线程置为当前线程,方便重入  
        else
//获取失败则加入等待队列
            acquire(1);
    }

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

//获取锁的方法 

protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update); //调用CAS设置锁的状态,如state为0置为1返会true ,否则返回false 获取锁失败
}

还记得前面在lock时 如果失败则会加入队列 ,那当前线程是怎样加入队列的呢? 其实 每个线程都会被封装成一个node节点,每个node节点都有一个前指针prev和后指针next 然后将一个一个node节点连接起来,

接下来我们分析竞争锁失败的情况 失败将会调用

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

我们先看  tryAcquire(arg)

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread(); //获取当前线程
    int c = getState(); //获取当前状态 0为可用,1为被锁住
    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;
}

tryAcquire 有两个作用,第一个作用是 再一次竞争锁,虽然线程在第一次竞争失败但有可能持有锁的线程持有时间非常短,

所以重新竞争

第二个作用是方便锁的冲入,一个线程可以重入该锁 并将计数加一,否则返回false 

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

上面的判断是个&&判断,当第一个为!tryAcquire(arg) 为假时直接返回,说明当前竞争的线程是冲入或者持有锁的线程释放后被线程获取,假设,线程没有获取到 则会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg))首先让我们先看下addWaiter(Node.EXCLUSIVE)方法 其中Node.EXCLUSIVE是个常量值 为空 

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;
}
private Node enq(final Node node) { //
    for (;;) {          //相当于while循环,知道下面设置成功才退出
        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;
            }
        }
    }
}

看到上面的函数没 ,该函数会无限重试,直至成功, 到这里最终会被加入到链表末尾,之后会执行 acquireQueued()函数是自旋锁的获取过程,具体的可以看另一位老铁的博客,acquireQueued的作用我也是参考它的博看才明白的下面是链接

https://blog.csdn.net/xxcupid/article/details/51891743

猜你喜欢

转载自blog.csdn.net/qq_32459653/article/details/81382508
今日推荐