java中锁的深入理解(二)

来源:互联网 发布:贵阳大数据广场在哪里 编辑:程序博客网 时间:2024/05/18 01:55

上一篇讲到了synchronized实现原理,这一篇一起来学习一下Lock的底层实现原理。

java.util.concurrent包中有很多lock接口的实现类,ReentrantLock,ReadWriteLock,这在我的另一篇文章中也提到了。

这些实现类的内部都使用了队列同步器AbstractQueuedSynchronizer类作为依赖。下面我们用AQS来指代AbstractQueuedSynchronizer。

AQS定义:

public abstract class AbstractQueuedSynchronizer extends    AbstractOwnableSynchronizer implements java.io.Serializable {     //等待队列的头节点    private transient volatile Node head;    //等待队列的尾节点    private transient volatile Node tail;    //同步状态    private volatile int state;    protected final int getState() { return state;}    protected final void setState(int newState) { state = newState;}    ...}
AQS中使用int型变量state来表示同步状态(0表示空闲,1表示被占中),通过内置的队列实现线程的排队工作。同步状态state,队列头指针head,队列尾指针tail都使用了volatile进行修饰,保证了多线程之间的可见性。

内部队列的定义:

static final class Node {        static final Node SHARED = new Node();        static final Node EXCLUSIVE = null;        static final int CANCELLED =  1;        static final int SIGNAL    = -1;        static final int CONDITION = -2;        static final int PROPAGATE = -3;        volatile int waitStatus;        volatile Node prev;        volatile Node next;        volatile Thread thread;        Node nextWaiter;        ...    }

队列中的每个节点存储了前缀节点prev,后缀节点next,节点中的线程thread,节点的状态waitStatus,状态的定义有四种,CANCELLED取消状态,SIGNAL等待触发状态,CONDITION等待条件状态,PROPAGATE状态需要向后传播。整个队列中,只有头节点中的线程是当前执行线程。

在并发的情况中,如果state的值为0,即状态为空闲,那么多个线程同时执行CAS修改state的值,成功修改state值的线程获得该锁。未获得锁的线程将生成新的节点,使用CAS的方式向后排队。当线程排到队列后面时,并不会马上插入,而是进行一个自旋操作。

final boolean acquireQueued(final Node node, int arg) {        try {            boolean interrupted = false;            for (;;) {                final Node p = node.predecessor();                if (p == head && tryAcquire(arg)) {                    setHead(node);                    p.next = null; // help GC                    return interrupted;                }                if (shouldParkAfterFailedAcquire(p, node) &&                    parkAndCheckInterrupt())                    interrupted = true;            }        } catch (Throwable t) {            cancelAcquire(node);            throw t;        }    }

因为在node的排队的过程,头节点中的线程(即之前在执行的线程)可能已经执行完成,所以要判断该node的前一个节点pred是否为head节点(代表正在执行线程),如果pred == head,表明当前节点是队列中第一个“有效的”节点,因此再次尝试tryAcquire获取锁,如果获取到了锁,就直接将该线程设置到头节点,否则,再进行判断:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {        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.             */            pred.compareAndSetWaitStatus(ws, Node.SIGNAL);        }        return false;    }
如果前缀节点的状态为SIGNAL,表示这个节点已经设置了锁一释放就通知它的状态,那么我们就可以安全的将线程插在它的后面,并返回true。如果前缀节点的状态是取消状态,那么就跳过前缀节点,找到前面没有被取消的第一个节点,插在其后面。否则,前缀节点的状态要么是0,要么是状态向后发散,我们直接将当前节点的状态设置为SIGNAL。以上两种情况,都无须向队列中插入,所以均返回false。

线程每次被唤醒时,都要进行中断检测,如果发现当前线程被中断,那么抛出InterruptedException并退出循环。从无限循环的代码可以看出,并不是被唤醒的线程一定能获得锁,必须调用tryAccquire重新竞争,因为锁是非公平的,有可能被新加入的线程获得,从而导致刚被唤醒的线程再次被阻塞,这个细节充分体现了“非公平”的精髓。

private void unparkSuccessor(Node node) {        /*         * If status is negative (i.e., possibly needing signal) try         * to clear in anticipation of signalling.  It is OK if this         * fails or if status is changed by waiting thread.         */        int ws = node.waitStatus;        if (ws < 0)            node.compareAndSetWaitStatus(ws, 0);        /*         * Thread to unpark is held in successor, which is normally         * just the next node.  But if cancelled or apparently null,         * traverse backwards from tail to find the actual         * non-cancelled successor.         */        Node s = node.next;        if (s == null || s.waitStatus > 0) {            s = null;            for (Node p = tail; p != node && p != null; p = p.prev)                if (p.waitStatus <= 0)                    s = p;        }        if (s != null)            LockSupport.unpark(s.thread);    }

线程释放锁的过程:

如果节点的状态小于0.将节点的状态设为0。然后找到下面状态值小于0的线程,将其唤醒。

总结:

Doug Lea大神写的代码相当牛,只看源码,不看应用,很难完全理解透彻,但源码看下来总体思路还是很清晰的。

0 0
原创粉丝点击