ReentrantLock之源码解析

28 篇文章 1 订阅
10 篇文章 0 订阅

   实现锁同步机制,我们一般可以通过

    (1) 自旋死循环实现缺点也很明显 空耗cpu

    (2) 通过自旋加 当前线程调用yied() 释放cpu进入就绪状态,当然这样当下次仍有可能会被选择执行

    (3) 通过自旋加 sleep() 让当前线程进入休眠状态 释放cpu , 这样似乎很完美,但是由于持有锁的线程执行时间不确定,可能造成当前线程的频繁唤醒

   (4) 通过 自旋加 Locksurport.park() 方法实现锁同步,当线程释放锁时通过 Locksurport.unprk(t1) 唤醒等待线程

   那么 jdk是如何做的呢?我们来看 ReentrantLock 是如何实现加锁的  

public void lock() {
    sync.lock();
}

那么这个sync是个什么, 何时初始化? 我们发现这个 sync 变量其实就是 AQS(AbstractQueuedSynchronizer) 他有两个实现类,公平锁和非公平锁 (AQS 通过维护一个类似与 CLS的双向链表的队列来实现公平加锁机制 )

通过源码我们发现其实 AQS 也是通过 对 状态 state 字段的值来控制锁的获得与释放,当获得锁state 值就 加 1,当释放锁或者说退出同步代码时state值减1当减到0时锁释放了

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

tryAcquire() 去尝试获得锁,首先会拿到当前线程,获得state 的值,state 为int 当第一个线程来拿锁时 state 为默认值0,然后会判断AQS的等待队列是否有线程在等待,第一个线程拿锁时队列是不存在的还没初始化 所以 hasQueuedPredecessors()返回false 然后直接 compareAndSetState 获得锁。那么这个hasQueuedPredecessors又是怎么实现的呢?

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;
}
其实也很简单 当第一个线程过来是 tail 和 head 多为null 所以 h != t 直接返回false 逻辑与 就不往后比较直接返回false, 当后续线程过来拿锁时 tail 和 head 一定不相同,下面我们就来分析为什么。
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;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

当后续线程拿锁时  ! tryAcquire(arg)  &&  acquireQueued(addWaiter(Node.EXCLUSIVE), arg)  方法tryAquire 尝试拿锁失败,执行 acquireQueued 关键就在 addWaiter方法,首先会创建好一个node放在内存中,判断 队列尾是否为空,第二个线程拿锁tail 为空,执行 enq方法 又是一个自旋 首先判断 队列尾是否为空 第二个线程过来 肯定为空,jdk 就创建了一个 空的 node 通过无参构造 其中的属性 均为设置。然后设置为head ,当死循环再次执行时队列尾就不为null,当前(t2)node 的 prev 设置为 tail 然后 cas设置 tail 即将 当前(t2)节点设置为tail 将 前面节点的 next 指向 node(t2)返回。

总结:enq 方法 第一进入死循环是初始化队列 创建一个空的node节点 将 head 和 tail 均指向 改空的节点,第二次进入时设置 队列尾部为 当前节点(t2)

                                    

                                                                         enq 第一次进入

                   

                                                                        enq 方法第二次进入

当第三个线程过来时,tail 肯定不为空,直接将 node(t3)的 prev 指向 队列尾,cas 设置tail 为当前 node3,  前置节点的next 指向 当前 node3直接返回.

private Node addWaiter(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;
}
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;
            }
        }
    }
}
Node() {    // Used to establish initial head or SHARED marker
}

第二个线程 获取锁进入队列等待之前,会再次 获取其前置节点 判断一下自己是否是队列中第一个等待的线程即其前置节点为head  p==head 会在尝试一次获取锁,失败就 调用 park 进入队列等待

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);
    }
}

上面说的是公平锁,现在说下非公平锁,非公平锁与公平锁的区别:非公平锁会在竞争锁时开始首先就强制的去cas设置锁并不关心等待队列是否为空,当抢锁失败会会再次判读一下锁是否处于自由状态,如果处于自由状态 即 state == 0 再次 cas 强制取锁,失败则加入等待队列之后的入队与公平锁一样。

总结:非公平锁无视等待队列,两次cas强制取锁失败后 加入等待队列排队取锁

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值