通过ReentrantLock学习AQS

一、排它锁,实现有公平锁和非公平锁两种。区别:公平锁吞吐量更小,能保持更高的有序性和减少饥饿性,但是不能保证完整的公平性。
二、推荐使用方式:

* <pre> { @code
* class X {
* private final ReentrantLock lock = new ReentrantLock();
* // ...
*
* public void m() {
* lock.lock(); // block until condition holds
* try {
* // ... method body
* } finally {
* lock.unlock()
* }
* }
* }} </pre>
三、lock
1.FairSync.lock:

final void lock() {
acquire( 1);
}
NoFairSync.lock:

final void lock() {
if (compareAndSetState( 0, 1))
setExclusiveOwnerThread(Thread. currentThread());
else
acquire( 1);
}
2.acquire:acquire方法封装在AQS中:
AQS.acquire(int arg):

public final void acquire( int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node. EXCLUSIVE), arg))
selfInterrupt();
}
解读:尝试tryAcquire(arg),如果成功,则表示获取当前线程获取所成功,如果失败则将当前线程放入CLH等待队列中。
注意:
  1. tryAcquire(arg)有AQS的继承类实现
  2. acquireQueue(...)是AQS实现的
所以,不同的实现类有不同的tryAcquire(arg)方法,这是AQS给继承类留的其中一个窗口。
关于selfInterrupt:具体来说,当对一个线程,调用 interrupt() 时,
① 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。仅此而已。
② 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。

interrupt和interrupted配合使用。
3.tryAcquire(int)在FairSync和NoFairSync中都有各自的实现。
先看FairSync.tryAcquire(int)的实现方法:

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;
}
1.new ReentrantLock()得到的对象,初始化state=0
2..hasQueuedPredecessors():
如果队列中有某thread排在当前thread前面,则返回true;
如果队列为空或者队列只有当前thread,则返回false。
3.
if(! this.hasQueuedPredecessors() && this.compareAndSetState( 0, var1)) {
this.setExclusiveOwnerThread(var2);
return true;
}
如果没有thread排在当前thread前面,且当前threadCAS成功,则使 exclusiveOwnerThread = 当前线程,并返回true。
注意:compareAndSetState是原子性的,所以如果多个线程同时调用此方法,只有一个线程成功,也就只有一个线程能够执行
this .setExclusiveOwnerThread(var2);
return true ;
不知道是不是这个原因, Doug Lea并没有对 setExclusiveOwnerThread 方法加volatile+CAS或者synchronized限制。

其实注意到后面的else if中的判断条件,也只有一个线程能够返回true,所以对于后续的方法并没有加入原子性限定,因为执行此方法的

再看NoFairSync.tryAcquire(int):

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) {
        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;
}
这个处理方式大致跟FairSync.tryAcquire差不多,主要不同的是,noFair不需要管等待队列CLH中是否有当前线程的祖先(processor),这也是noFair的原因。
4.如果tryAcquire(int)失败,则执行后续操作,先看addWaiter方法:
在解析addWaiter之前先了解Node:


对Node介绍比较详细的参考:
另外,补充非常重要的一点,也是我之前的困惑:对于CLH队列和Conditions队列的区别,只知道他们是不同的队列,但是具体的细节没有摸清楚。现在有一段能很好解答我疑问的源码注解:
* <p> Threads waiting on Conditions use the same nodes, but
* use an additional link. Conditions only need to link nodes
* in simple (non-concurrent) linked queues because they are
* only accessed when exclusively held. Upon await, a node is
* inserted into a condition queue. Upon signal, the node is
* transferred to the main queue. A special value of status
* field is used to mark which queue a node is on.

1.CLH和Conditions队列的节点单元都是Node。
2.CLH是双向,Conditions是单向队列。
3.只有在exclusive情况下才会用到Conditions队列。
4.CLH队列中的Node在某种情况下,将某个或某些Node移到Conditions队列中,在某种情况下被移回CLH。
5.Conditions算是CLH等待队列的过度队列。

AQS.addWaiter(Node mode):

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 (;;) {
        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;
            }
        }
    }
}
真正有意思的底层代码!!!但这两部分还是挺简单的。
步骤:
1.拿出tail(prev=tail),判断tail是否为空,不为空,则node.prev = tail;如果为空,则执行步骤3
2.尝试将node置为tail,如果置换成功,则prev.next=node,这样就成功在尾部加入node,且使node成为新的tail;如果置换失败,则执行步骤3
3.enq(node),先判断tail(prev=tail)是否为null,如果是,说明CLH队列还未创建,先创建CLH队列,且使head=tail=new Node,(这个new Node也有说法,他其实是一个空thread,说明head是一个无意义节点。);如果不为空,则执行步骤2。整个过程放在无线循环for里,也很容易想到原因:多个线程同时执行addwaiter,那也得一个一个放入tail中,毕竟CAS(tail,node)是原子性操作。
5.我们先要搞清楚,addWaiter到底干了什么?它其实是在当前thread在第一次尝试获取锁之后,不得不进入等待队列的操作。
这就是addWaiter,它让当前thread进入等待队列。但仅此而已,因为addWaiter并没有让当前thread进入阻塞状态(park),以及在
无限循环中伺机获取锁,并继续执行(unpark)。而这,是 acquireQueued( final Node node, int arg) 要干的事。

但在介绍 acquireQueued( final Node node, int arg) 之前,我需要搞清楚这个 waitStatus到底是什么?其实在之前看过很多
类似状态位,各种状态,而waitStatus目的很强,但要真正理解这几个状态位的意思,或者说是用途,还是要结合 acquireQueued( final Node node, int arg) 
来看,尤其是SIGNAL,为什么偏偏是要在遇到前继节点(是泛指,只要排在当前节点前面,都是前继节点)是SIGNAL时,就能把当前线程状态操作成阻塞呢?其实很简单,设计者在想到
SIGNAL的用途就是这样:前继节点是SIGNAL状态了,后继节点就能进入park了。 SIGNiAL就像是一个接力棒,Node是认SIGNAL的,
就好比人,只要排在拥有接力棒的人后面,总能轮到自己拿到这个接力棒的。(我是不是很有才!!!)

CANCELLED[ 1 ] -- 当前线程已被取消
SIGNAL [- 1 ] -- “当前线程的后继线程需要被unpark(唤醒)”。一般发生情况是:当前线程的后继线程处于阻塞状态,而当前线程被release或cancel掉,因此需要唤醒当前线程的后继线程。 CONDITION[- 2 ] -- 当前线程(处在Condition休眠状态)在等待Condition唤醒 
PROPAGATE[- 3 ] -- (共享锁)其它线程获取到“共享锁”
[ 0 ] -- 当前线程不属于上面的任何一种状态。初始状态

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);
    }
}
1.首先这是在无限循环中执行的
2.如果当前线程在论资排辈终于排到它了,很兴奋,马上tryAcquire获取锁,如果成功,改写head,自己成为head,将前head注销,以便GC。并且自己处于head的时候,node.thread(即head.thread )=null。仔细想想看,Node的作用就是让排在CLH对了的thread获取锁,而此时既然已经获得到了锁,此Node的目的就达到了,那么node.thread就没有存在的必要了(占内存,兄弟)。另外,获取到锁之后,就能让当前线程interrupt了,前面已经介绍了interrupt的作用。
3. shouldParkAfterFailedAcquire:在没轮到自己之前,找一个SIGNAL状态的前继,接在它后面。遇到CANCELLED的Node就移除它,遇到waitStatus=0或PROPAGATE的,就将其状态改为SIGNAL,这样就能最终实现返回true,个人认为返回false的可能性为0,置于后面要return false,完全是因为代码层面的返回需要。
4. parkAndCheckInterrupt:一旦寻找到有或者自我创造带有SIGNAL的前继Node,就能安心park了。
if ( shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
这段代码会一直执行,知道当前线程排到队列的头部了。
5.parkAndCheckInterrupt:
private final boolean parkAndCheckInterrupt() {
LockSupport. park( this);
return Thread. interrupted();
}
结合:
if ( shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
再结合知识点:
调用线程的interrupt方法,并不能真正中断线程,只是给线程做了中断状态的标志Thread.interrupted():测试当前线程是否处于中断状态。执行后将中断状态标志为false
Thread.isInterrupte(): 测试线程Thread对象是否已经处于中断状态。但不具有清除功能
发现:如果在parkAndCheckInterrupt 中返回的是true,说明此thread是interrupted状态,但是
Thread.interrupted() 会改变thread的状态,所以需要重新将其变为interrupted,所以才有了
public final void acquire( int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node. EXCLUSIVE), arg))
selfInterrupt();
}
selfInterrupt 。

6.如果失败获取锁,这里失败的原因,查看代码,只有在
protected boolean tryAcquire( int arg) {
throw new UnsupportedOperationException();
}
情况会发生。
执行cancelAcquire操作:
看源码会执行一些清除cancelled状态的Node,并 在某种情况下,通知后继node继续执行unparkSuccessor(node)

总结:要实现AQS,比如ReentrantLock,步骤:
1.定义一个实现类,去继承AQS,并实现.lock方法,此方法无论怎么变行,会调用AQS的acquire(int)方法
2.在实现类中去定义tryAcquire(int)方法,这会决定是exclusive mode还是share mode

3.将此实现类作为外层类(如ReentrantLock)的一个属性

四、unlock
unlock相对简单,直接AQS.release(int):

public final boolean release( int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h. waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
一开始我有疑问:为什么unparkSuccessor不是从当前线程开始去找它的后继者。但是知道撸过lock之后就知道,原因有两:
1.在lock过程中,当前thread成功获取锁之后,会将Node.thread=null
2.在获取锁后,当前node是会被设置为head节点,所以这里直接从head开始,没毛病。
要学习Condition queue,其实LinkedBlockingQueue是一个非常好的学习例子。
明天有时间,把手写笔记摘抄完善一下。

还有其它重要方法,未完待续。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值