ReentrantLock加锁与解锁

0. 我先梳理一下公平锁的流程,非公平锁是在公平锁的基础上多了两次抢锁的过程,也就是不公平的地方
公平锁上来就是 acquire(1)

final void lock() {
            acquire(1);
        }

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

**acquire() 方法里面调用了三个方法 :tryAcquire, addwaiter 和 acquireQueued **
tryAcquire会判断当前锁是否是自由的,即 state == 0? 如果是,那么就看队列是否有人在排队 hasQueuedPredecessors(),没有的话直接就CAS成功获取到锁了,或者是重入锁,否则失败的话就需要入队列,其实这里也有点讲究,分为三种情况,

  • 线程T1是整个锁的第一个顾客
  • 线程T2是整个锁的第二个顾客
  • 线程T3及以上,是整锁的第三 、四、五 。。。个顾客
 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());
    }

如果是第一个顾客T1,此时 tail == head == null ,没人初始化,所以 hasQueuedPredecessors返回false,然后CAS改状态并且设置OwnerThread,就return true 加锁成功,没有初始化队列,否则判断是否是重入,不然就失败 返回false;

if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
  • 如果tryAcquire(arg) 返回True,那么**!tryAcquire(arg)**
    就是False,后面就不用走了,直接一路返回,加锁成功!所以有一个关键,第一个线程和队列无关,队列没有初始化它就返回了
  • 返回false,说明获取锁失败,就来一次acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),这里才是和队列相关的地方。设置完队列后 head!= tail。
  • 初始化队列后,这里第二个顾客和第三及以上的顾客又有一个区别,就是如果pre节点是head,那么他就会再尝试获取锁,失败了就pack(),而第三个以上的节点直接放弃治疗了,直接pack();
        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;
        }
    }
  • 对于非公平锁,一旦判断c == 0,即锁是自由的,直接就CAS改状态
公平锁:
if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
非公平锁:
if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }

1. ReentrantLock非公平锁加锁流程
首先尝试CAS的方式将state从0改为1,改成功就将当前的线程所有者改为自己。这里体现了第一次不公平,一上来就CAS和改owner线程,而公平锁的话获取acquire(1)获取锁

final void lock() {
	//调用CAS尝试获取锁
    if (compareAndSetState(0, 1))
    	//尝试成功则修改当前拥有锁的线程是自己
        setExclusiveOwnerThread(Thread.currentThread());
    else
    	//获取失败
        acquire(1);
}

如果失败,就调用 acquire(1)

public final void acquire(int arg) {
	**//tryAcquire:尝试获取锁,加取反则表示获取失败后执行&&后面的方法
	//addWaiter:将当前线程封装为Node后添加到同步队列尾部
	//acquireQueued:线程入队后再次尝试获取锁或者阻塞**
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

2. 尝试获取锁 tryAcquire
尝试获取锁默认是获取非公平锁,首先看程序当前线程的状态getState(),如果是0,说明没人用,那么就CAS去改,改成功就行了,如果不是0,还要看看是否是自己当前已经拿着锁了,毕竟可重入,是的话state的数值就要再加,算是重入了吧,如果不是那就获取不到了,返回false

protected final boolean tryAcquire(int acquires) {
	//非公平的尝试获取锁
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
	//获取当前线程
    final Thread current = Thread.currentThread();
    //获取当前锁状态
    int c = getState();
    //如果锁空闲
    if (c == 0) {
    	//调用CAS尝试获取锁
        if (compareAndSetState(0, acquires)) {
        	//尝试成功则修改当前拥有锁的线程是自己
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //如果锁仍被持有而且持有锁的线程是自己
    //这里体现了ReentrantLock的可重入性
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        //锁状态state不能为负值
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        //修改state,不使用CAS是因为此时不可能有其他线程可以修改state
        setState(nextc);
        return true;
    }
    //获取锁失败
    return false;
}

3. 将线程包装成node节点添加到队列
将线程包装成node节点,然后找到尾巴tail节点,如果tail不是null,则将node接在tail后,再用CAS的方式将tail指向node
如果tail是null,说明队列还没有初始化或者上面的CAS将tail指向node失败(其他线程已经改了),那么就调用enq(node);

private Node addWaiter(Node mode) {
	//给当前线程新建Node结点,mode表示是排它锁还是共享锁
    Node node = new Node(Thread.currentThread(), mode);
    //pred指向尾结点
    Node pred = tail;
    //如果尾结点不为空,意味着队列已经被初始化
    //但不一定有线程在队列中等待,因为队列的头结点对应的线程永远是null
    if (pred != null) {
    	//让新建的结点的前驱结点指向尾结点
        node.prev = pred;
        //调用CAS尝试修改新建结点为尾结点
        if (compareAndSetTail(pred, node)) {
        	//尝试成功则设置原来的尾结点的后继结点指向新建结点
        	//如果修改失败则意味着此时有其他线程加入队列且修改了尾结点
            pred.next = node;
            return node;
        }
    }
    //如果队列还没有被初始化或者上面的CAS失败则调用enq
    enq(node);
    return node;
}

4. node加入到队列中
enq 一上来就是一个自旋 for(;😉 死循环,保证节点必能添加到队列中。如果tail== null,说明队列还没有初始化,就把head 设置为一个空节点(new Node()),然后tail指向head,此时tail就是空节点而不是null;不管怎样,第一次自旋没戏了,接着第二个自旋,如果tail != null,说明是有线程修改了。不管是第二次自旋还是一开始tail == null, 现在都要进入那个else 代码块: 还是和第三步一样将节点接到tail 后面,再CAS更改tail节点

private Node enq(final Node node) {
	//死循环即自旋,保证了所有进入此方法的结点都能被正确的添加到队列中
    for (;;) {
    	//t指向尾结点
        Node t = tail;
        //如果尾结点为空则意味着队列还没有被初始化
        if (t == null) { // Must initialize
        	//调用CAS尝试新建线程为null的结点作为头结点
            if (compareAndSetHead(new Node()))
            	//尝试成功则将尾结点也指向头结点,失败则进入下一次自旋
                tail = head;
        } else {	//如果队列已经被初始化
        	//让新建结点的前驱结点指向尾结点
            node.prev = t;
            //调用CAS尝试修改新建结点为尾结点
            if (compareAndSetTail(t, node)) {
            	//尝试成功则设置原来的尾结点的后继结点指向新建结点
                t.next = node;
                return t;
            }
        }
    }
}

5.线程尝试获取锁失败且成功入队后
node节点入队列后就会开始自旋(死循环),一开始有个获取锁的标志位,failed = false, 和一个中断标志位 interrupted = false, 然后自旋。

  • 如果node的前面就是head,说明是队列里头的第一个,就有机会能去拿锁,那就把node放在设置为head,然后原来的head GC 掉。
  • 否则判断这个线程是否应该被挂起,shouldParkAfterFailedAcquire(p, node),parkAndCheckInterrupt(),如果判断成立,那就挂起,中断设置为true。
final boolean acquireQueued(final Node node, int arg) {
	//获取锁失败的标识
    boolean failed = true;
    try {
    	//被中断的标识
        boolean interrupted = false;
        for (;;) {
        	//p指向新建结点的前驱结点
            final Node p = node.predecessor();
            //如果p是头结点即node是队列中第一个等待的线程
            //则再次调用tryAcquire尝试获取锁
            if (p == head && tryAcquire(arg)) {
            	//获取成功则设置当前结点为头结点(对应线程永远为null)
                setHead(node);
                //设置原来的头结点的后继节点为null,那么该结点不再
                //被任何地方引用也不再引用任何其他对象,由GC负责回收
                p.next = null; // help GC
                //获取锁成功标识
                failed = false;
                //这里返回false则表示线程没有被申请中断
                return interrupted;
            }
            //shouldParkAfterFailedAcquire:判断当前线程是否应该被挂起
            //parkAndCheckInterrupt:挂起线程,清空线程的中断标识并返回
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

6. 判断线程是否应该挂起
先判断node的pre节点的状态,如果是signal,那么就可以挂起,如果是1(canceled),说明该pre节点应该终止了,不在参与锁竞争,那么就从pre起往前找,知道找到第一个不是canceled状态的节点。node.prev = pred = pred.prev;
如果既不是signal,也不是 canceled,那么尝试将pre节点的转态改为 signal compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	//获取node的前驱结点pred的状态标识
	//SIGNAL值为-1,标识该结点的后继结点需要被唤醒
	//CANCELLED值为1,标识该结点被终止,不再参与锁竞争
    int ws = pred.waitStatus;
    //如果node的前驱结点pred的状态标识为SIGNAL
    if (ws == Node.SIGNAL)
    	//可以被挂起
        return true;
    //如果node的前驱结点pred的状态标识为CANCELLED
    if (ws > 0) {
    	//队列从后往前清除状态标识为CANCELLED的结点
        do {
        	//这是第一个可能删除结点的地方
        	//从后往前直到第一个SIGNAL结点间的所有CANCELLED结点被删除
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        //设置前驱结点pred的后继结点为当前结点node
        pred.next = node;
    } else {
        //若前驱结点pred的状态标识为初始状态则修改为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    //暂时不能被挂起
    return false;
}

private final boolean parkAndCheckInterrupt() {
	//系统调用挂起线程,线程在这里真正被阻塞
	//从这里被唤醒有两种可能:一是unpark(),二是interrupt()
    LockSupport.park(this);
    //清除线程中断标识并返回
    return Thread.interrupted();
}

7. 解锁操作
尝试释放锁,其实就是state -1,设置owner线程为 null ,释放成功就唤醒下一个节点

public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
	//尝试释放当前锁
    if (tryRelease(arg)) {
    	//h指向头结点
        Node h = head;
        //如果队列非空且头结点的状态标识为SIGNAL
        if (h != null && h.waitStatus != 0)
        	//唤醒头结点的后继结点
            unparkSuccessor(h);
        //释放成功
        return true;
    }
    //释放失败,意味着该锁被重入了,释放次数<加锁次数
    return false;
}
protected final boolean tryRelease(int releases) {
	//获取锁状态state并减去要释放的数量即1
    int c = getState() - releases;
    //如果释放锁的线程不是拥有锁的线程则抛异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //如果释放一次锁后锁状态变为空闲
    if (c == 0) {
        free = true;
        //修改当前拥有锁的线程为null
        setExclusiveOwnerThread(null);
    }
    //修改锁状态为0即空闲
    setState(c);
    return free;
}

唤醒下一任
先获取当前node的状态,如果是signal,就设置为0,然后交给下一任,需要从尾巴到头找到最先的不是canceled的node,作为下一任

private void unparkSuccessor(Node node) {
	//获取当前结点的状态标识,此时可能为SIGNAL或者CANCELLED
	//如果为CANCELLED意味着node已经被唤醒但发现自己被中断需要继续往后唤醒新的结点
    int ws = node.waitStatus;
    //如果当前结点状态标识为SIGNAL
    if (ws < 0)
    	//调用CAS修改当前结点状态标识为0即初始状态
        compareAndSetWaitStatus(node, ws, 0);
    //s指向当前结点的后继结点
    Node s = node.next;
    //如果队列中没有等待的线程或者s的状态标识为CANCELLED
    if (s == null || s.waitStatus > 0) {
        s = null;
        //队列从后往前找到最后一个(也就是从前往后第一个)
        //状态标识不是CANCELLED的结点,且不能是当前结点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //如果队列中有等待被唤醒的线程
    if (s != null)
    	//系统调用唤醒第一个等待的线程
    	//被阻塞的线程会在parkAndCheckInterrupt()中继续向下执行
        LockSupport.unpark(s.thread);
        
}

8. 最后是一个cancelAcquire方法

自己的理解:当一个node获得锁后,它自身就会被置为NULL节点,然后GC,当一个node调用cancleAcquire后,首先这个Node的thread就会被置为null,然后找到它的前驱节点 pre ,如果 pre 是坏的(状态值大于0),那么就继续往前找,直到找到一个好的,然后把这个Node状态置为cancel

  • 如果node是tail,那就把 pre 通过CAS改为tail
  • 如果不是tail , 就把 pre.next = node.next,类似链表的操作
  • 或者node已经是head,那就唤醒下一个节点
private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;

        node.thread = null;

        // Skip cancelled predecessors
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        // predNext is the apparent node to unsplice. CASes below will
        // fail if not, in which case, we lost race vs another cancel
        // or signal, so no further action is necessary.
        Node predNext = pred.next;

        // Can use unconditional write instead of CAS here.
        // After this atomic step, other Nodes can skip past us.
        // Before, we are free of interference from other threads.
        node.waitStatus = Node.CANCELLED;

        // If we are the tail, remove ourselves.
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            // If successor needs signal, try to set pred's next-link
            // so it will get one. Otherwise wake it up to propagate.
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值