ReentrantLock解析

该锁是 独占锁 但是分为 公平 非公平
公平 指的是 先进先出的概念 而 非公平 只要state=0 不管 有没有排队 都能有机会cas拿到锁

AQS 主要负责了 维护 Node双向链表 等待的节点 的操作,获取 更改 state等

ReentrantLock 源码相对比较简单 读者 可以 读过之后再去看 readWriteLock 会亲轻松很多

public void lock() {
    sync.lock(); 当点机时 会出现公平非公平 上述已有说  两者差别不是很大
}

public final void acquire(int arg) {
    if (!tryAcquire(arg) && 尝试获取锁
    	获取不成功 那么加入到等待链表种  然后 自旋 2次没拿到锁 就park 等待 拿到锁的线程唤醒
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}


    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
		如果 等于0 代表 没有线程 拿到这把锁 【独占】
        if (c == 0) {
        	看下面方法详解   公平 非公平 只是少了这个方法而已
            if (!hasQueuedPredecessors() &&
            如果等待链表不存在 或者当前线程就是 headNode.nextNode 应该唤醒的线程那么
            直接cas 尝试拿锁
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        如果上面不满足 看看是不是 重入  如果是 重入的状态 那么也返回 ture
        重入一次加1 拿到锁也是加1
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

	这个方法主要是为了 拦截的 拦截 虽然state=0了 但是 他不是 headNode.nextNode.thread 那么同样加入到等待节点链表中
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());
}


如果 返回fasle 那么需要 加入到等待链表中 使得线程watting
private Node addWaiter(Node mode) {
封装一下  reentrantLock默认是独占的 所以这个node 也是 一个标记的意思
    Node node = new Node(Thread.currentThread(), mode);
   
	如果tail节点 不等于空代表不是第一次进来了 ,那么直接 把自己设置成 尾部节点
    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;
                }
            }
        }
    }

	加入到 双向链表中之后  这儿 开始真正的 自旋获取锁 但是默认只有2次机会
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
        中断标记 不是很重要  在可中断的lock中有用
            boolean interrupted = false;
            for (;;) {
            获取preNode  因为在 设计之初就定义了 即使被唤醒 也要判断 当前节点的上一个节点
            是不是 头节点 按序唤醒 【这与公平 非 公平 无关】
            headNode代表了持有锁的线程 
                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);
        }
    }


	如果等于 -1 代表后续有线程需要 当前拿到锁的线程唤醒  如果> 0 代表canncel 
	private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
		获取peNode的状态 如果是signal代表后续线程 也就是当前线程需要被前驱节点线程唤醒
		然后 跳过 状态 等于1>0 代表该等待节点取消了等待 ,移除   然后cas设hi前驱节点的waitState=signal 然后再次 循环 的时候 就安心 park 了
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }


=============释放锁===========================


    public final boolean release(int arg) {
    尝试释放
        if (tryRelease(arg)) {
            Node h = head;
           释放成功看看有没有需要 唤醒的节点 如果有 执行unpark
            if (h != null && h.waitStatus != 0) 
             这个地方其实会有个问题 就是
            一个等待的线程 在加入到节点链表中之后 但是没拿到cpu的执行器 waitStatus并没有改掉 然后切到 拿到锁的线程一直执行,就会无法unpark 但是就是这么设计的 也没啥好说的													
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

	这个方法 首先 改变 当前节点 wait状态=0初始话 因为 当前节点已经  可以丢弃了
	然后找到下一个节点 唤醒  等于null方向遍历  因为enq 中cas后 没有把nextNode存到 上一个节点中 所以需要方向
	 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);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值