AbstractQueuedSynchronizer与ReentrantLock。排他锁实现分析

来源:互联网 发布:java环境一键安装包 编辑:程序博客网 时间:2024/05/16 00:27

一篇较全的文章:http://ifeve.com/introduce-abstractqueuedsynchronizer/。另外并发编程网也有关于aqs的doug lea的论文翻译,可以参考。

AbstractQueuedSynchronizer关键方法

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);        }    }
 这方法进行了关键性的一步,setHead的时候,解除了前驱节点关联,同时p.next=null解除了后驱节点的关联。当一个节点被设置为头节点的时候,会解除对前驱节点的关联。

这里应该是整个排他锁实现比较关键的一步了。

在此方法之前的步骤基本没有什么特别之处,基本上是通过不断的cas,让自己成为一个尾节点(中间过程可能包括初始化头节点,尾节点enq),并且和之前的节点建立双向关联。

看这里的for(;;),之前让我产生误解的地方,开始误以为这里既是论文中提到的自旋锁过程。其实这里是一个简单的重试,这里的for也为非公平锁提供了一个闯入的机会。一旦某个线程执行到这一步是不可能具有非公平锁的,这里是指给其他线程提供了闯入的机会。

来看看如何闯入

ReentrantLock中非公平锁尝试获取锁的代码:

final boolean nonfairTryAcquire(int acquires) {            final Thread current = Thread.currentThread();            int c = getState();            if (c == 0) {                if (compareAndSetState(0, acquires)) {                    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;        }

getState()根据aqs的api描述,是自定义含义的。在ReentrantLock中,其实这里代表了是否有线程正在进行,或者说,是否有线程拿到了锁。这里和公平锁最大的区别在于是否判定有等待队列。只要发现getState值为0,则,立即开始尝试更新state的值,一旦更新成功这会导致任何公平或者非公平的非公平锁请求直接进入chl队列。


返回上面提到说acquireQueued方法提供了闯入机会,是因为调用逻辑中,如果shouldParkAfterFailedAcquire(p,node) && parkAndCheckInterrupt() 成功执行,那么线程会block住,等待上个线程释放锁最终调用LockSupport.unpark(t),那么这个线程才会继续开始运行。在刚被唤醒的时候,这个时候只要acquireQueued中调用tryAcquire(arg)没有成功执行(准确的说是state变量没被成功设值)。那么非公平锁,就有机会提前抢到执行机会。整个过程中,aqs中的state变量担当了重要角色。


还有一个非锁竞争关系的问题,如果释放锁,刚好调用到release(int arg) 调用unparkSuccessor(h),unparkSuccessor(h)又刚好执行完,另外一个线程又才进入队列也设置了上个节点等待状态为Node.SIGNAL,准备执行if (shouldParkAfterFailedAcquire(p,node) && parkAndCheckInterrupt())。当执行shouldParkAfterFailedAcquire以后必然调用 LockSupport.park(this);问题来了,岂不是这个线程会永远的block?不会block的原因在于,unparkSuccessor(h)中如果提前调用了 LockSupport.unpark(s.thread);那么会导致随后的LockSupport.park方法不会block线程。


刚开始,没看懂的几个问题:被构建的Node如何解除双向关联,
                      如何提供非公平锁的闯入时机,release(int arg) 方法调用,成功设置了state值为0的时候,已经对非公平锁提供了闯入时机。
                      如果release的执行先于最终调用park方法会不会造成线程一直block。


                   


 
0 0
原创粉丝点击