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() &&  //hasQueuedPredecessors方法,当节点有前继会放回true,而只有头节点没有
            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;
}

  //该方法可以保证能从,头节点开始唤醒

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    //解释一下什么时候会返回真,当h!=t意味着,有节点在等待,(s=h.next)==null,意味着下一个
   //节点为空,意味着当前节点可以运行,如果不为空,则比较当前线程是否是节点s中的线程
   //如果不是,返回真,意味着当前线程,不是头节点的next的中的线程,则放回true;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

 

 

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

 

另外我们知道ReentrantLock有公平锁和非公平锁,那么ReentrantLock是如何实现这个机制的呢?具体请看公平锁与非公平锁lock锁的方法了 ,对于非公平锁,lock方法

final void lock() {
    if (compareAndSetState(0, 1)) //注意这里,这里先尝试设置,而不直接调用
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);//失败,则进入这里
}

对于公平锁

final void lock() {
    acquire(1);//直接调用acquire方法,而在acquire方法中,如果所没有被持有只有头节点才能获取锁
}

 

而对于公平锁调用acquire(1)会调用此方法

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
if (!hasQueuedPredecessors() && //这一个方法是控制是否公平的关键,对比公平锁与非公平锁的tryAcquire实现你会发现只有这里不

                                //同 ,任何并发包能够实现公平与非公平都与该实现有关
            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;
}
//非公平锁,的实现,
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {//这里是主要关键0,只要是0就可以尝试获取,这是与公平锁不同的地方
            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;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值