AbstractQueuedSynchronizer笔记

除了内置锁synchronized以外,还有ReentrantLock和ReentrantReadWriteLock实现防止临界资源的竞争,也就是所谓的编程式锁,内部实现AbstractQueuedSynchronizer,以下简称AQS,AQS也是JUC包中Semaphore,CountDownLatch,CyclicBarrier的基础。

ReentrantLock可以实现资源访问互斥。

ReentrantLock lock = new ReentrantLock();
     。。。   
        try{
            lock.lock();
            //临界资源
            。。。
        }finally {
            lock.unlock();
        }

ReentrantReadWriteLock可实现读读兼容,读写加锁不兼容,写写不兼容。

ReadWriteLock lock = new ReentrantReadWriteLock();
Lock write = lock.writeLock();
Lock read= lock.readLock();
。。。
try{
    write.lock();
     。。。
 }finally {
   write.unlock();
 }
或
。。。
 try{
     read.lock();
     。。。
 }finally {
    read.unlock();
 }

锁的资源也就是临界资源在lock()和unlock()中,先lock()然后在finally 中unlock(),为什么要这样做?如何实现保证同步 ,与内置锁有哪些优势和区别?

概要

本想从ReentrantLock实现上引出AQS,但是篇幅较大,越写越把AQS落在末尾,读起来十分不顺,最后弄砸了。这里将先从AQS实现介绍,再介绍ReentrantLock和ReentrantReadWriteLock,Semaphore,CountDownLatch实现。

AQS

基本结构

  • 结构
    AQS继承了抽象类AbstractOwnableSynchronizer
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

AbstractOwnableSynchronizer是一个抽象类接口,无具体实现,

  • 属性
    AQS有三个属性
private transient volatile Node head;
private transient volatile Node tail;
/**
 * The synchronization state.
*/
private volatile int state;

这里state表示同步状态,互斥锁中要求state状态只能由一个线程变更,不能获得锁即意味着是线程需要等待,也就是进入一个CHL队列,head表示队列头部,tail队列尾部。

  • Node
    以下代码可以看出,Node为双向链表(prev和next表示前,后节点),节点值为Thread ,waitStatus表示状态,还有条件队列nextWaiter。
 // waitStatus可取值 0、CANCELLED(1)、SIGNAL(-1)、CONDITION(-2)、PROPAGATE(-3)
 volatile int waitStatus;
 volatile Node prev;
 volatile Node next;
 volatile Thread thread;
 //条件队列或是共享锁状态,添加队列使用
 Node nextWaiter;

waitStatus状态:

  • CANCELLED(value:1):因超时或中断造成,是最终态,节点对应线程不在阻塞;
  • SIGNAL(value:-1):继任节点是阻塞或者很快变成阻塞,当节点在释放锁或取消状态中,会将继任节点线程从block状态中unpark出来;
  • CONDITION(value:-2):条件队列(调用Condition),下一个状态为0;
  • PROPAGATE(value:-3):传播共享锁。

功能点

锁分互斥锁、共享锁,互斥锁保证对临界资源访问同一时间只有1个线程,共享锁保证读读不加锁,写读或读写加锁;还有特性要求,要求访问临界资源的请求按照访问先后顺序,进入临界资源(即公平锁),也有要求访问时不按照先后顺序访问(即非公平锁)。

属性state表示临界资源的状态,state改变通常由实现类通过AQS#compareAndSetState去更新,如ReentrantLock中大于0表示有线程持有该状态,等于0表示无线程持有。互斥锁要求AQS实现类实现tryAcquire(尝试更新锁状态,返回值大于0表示该获取线程获得临界资源执行状态,无需排队,小于等于0需要排队执行。公平锁可以返回0,排队执行;非公平锁,线程进入时可尝试更新state获取锁,无需排队)、tryRelease(释放锁),共享锁则是tryAcquireShared、tryReleaseShared。

 * <p>To use this class as the basis of a synchronizer, redefine the
 * following methods, as applicable, by inspecting and/or modifying
 * the synchronization state using {@link #getState}, {@link
 * #setState} and/or {@link #compareAndSetState}:
 *
 * <ul>
 * <li> {@link #tryAcquire}
 * <li> {@link #tryRelease}
 * <li> {@link #tryAcquireShared}
 * <li> {@link #tryReleaseShared}
 * <li> {@link #isHeldExclusively}
 * </ul>

互斥锁实现上,AQS内部实现了方法acquire,acquireInterruptibly,tryAcquireNanos方法(这些方法内部调用tryAcquire,tryAcquire返回true表示获得临界资源执行权限,即获得锁,false表示未获得锁,需要排队执行),目的是去获取锁,未获得锁时将线程进入等候队列,实现类(如ReentrantLock,ReentrantReadWriteLock)调用这些方法,实现了诸如lock()、lockInterruptibly()、tryLock()、tryLock(long timeout, TimeUnit unit);锁释放,提供release(int arg) 方法给实现类使用,release内部调用了tryRelease。

共享锁实现上,提供了acquireShared(int arg),acquireSharedInterruptibly(int arg),tryAcquireSharedNanos(int arg, long nanosTimeout)方法,内部实现上调用了实现类的tryAcquireShared判断临界资源状态;锁的释放上,有releaseShared(int arg) ,内部调用了实现类的tryReleaseShared。

实现原理

对acquire,acquireInterruptibly,acquireShared等内部方法进行解读,理解AQS的实现原理。

互斥锁

获取锁方法有acquire,tryAcquireNanos,acquireInterruptibly方法,锁释放有release(int arg)。

acquire

调用实现类的tryAcquire,返回值true当前线程获得锁,返回false表示未获得锁线程需要入CHL等候队列排队执行。

 public final void acquire(int arg) {
          if (!tryAcquire(arg) &&
           //构造排他节点进入FIFO队列
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //selfInterrupt()未做任何特殊处理,将线程发生的中断状态传递给外层调用
            selfInterrupt();//Thread.currentThread().interrupt();
    }

在看addWaiter方法,首先将线程,还有指定互斥模式作为参数构造Node节点,优先用CAS将节点设置为tail节点,如果存在并发出现CAS失败,调用enq入队,enq中自旋方式调用CAS确保节点进入队列尾部。

 /**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    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;
    }

   /**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    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;
                }
            }
        }
    }

acquireQueued方法中会不断尝试获取锁,先检查前一个节点是否是head节点(head节点表示当前节点在执行或已经执行完毕),如果是head节点且tryAcquire返回true,表示获取锁成功,将当前节点设置为head节点(同时将pre=null,前节点的next=null);获取锁失败,会检查是否应该block(前节点是SIGNAL,待执行或在运行,当前线程需要阻塞),如果应该阻塞,调用parkAndCheckInterrupt进行阻塞,并返回线程是否是中断状态。

/**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            //检查线程中断状态
            boolean interrupted = false;
            for (;;) {
                //前一节点
                final Node p = node.predecessor();
                //如果前节点是头节点,调用tryAcquire()再次尝试为当前线程获得锁
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
               //shouldParkAfterFailedAcquire检查节点是否应该block
               //parkAndCheckInterrupt中调用方法阻塞线程,返回线程是否是处于中断状态
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            //属于特殊情况。如果未执行到failed = false时发生异常,这里才会执行。
            //cancelAcquire会节点状态变成取消,从队列节点中剔除。
            if (failed)
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire方法,前节点为SIGNAL状态,返回true

 /**
     * Checks and updates status for a node that failed to acquire.
     * Returns true if thread should block. This is the main signal
     * control in all acquire loops.  Requires that pred == node.prev.
     *
     * @param pred node's predecessor holding status
     * @param node the node
     * @return {@code true} if thread should block
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        //前一节点SIGNAL,返回true
        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.
             */
            //如果前面连续节点是因超时或中断变成CANCELLED状态,需要从链表中剔除这些节点
            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.
             */
            //如果节点是初始状态0或PROPAGATE,将pred(前一节点)设置SIGNAL状态
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

调用LockSupport#park阻塞当前线程,并返回线程是否处于中断状态。

  /**
     * Convenience method to park and then check if interrupted
     *
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

acquireInterruptibly

顾名思义,在抢锁过程中有线程中断则不能获取锁,抛出InterruptedException。

public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

doAcquireInterruptibly大致实现同acquireQueued,一点点区别,返回值是void不是true,还有在获取锁过程中有中断行为则抛出InterruptedException,将节点从队列中删除。

 /**
     * Acquires in exclusive interruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            //发生中断或异常,failed =true,cancelAcquire方法会将
            //节点状态变成取消,从队列节点中剔除。
            if (failed)
                cancelAcquire(node);
        }
    }

tryAcquireNanos(int arg, long nanosTimeout)

和acquire区别在于:如果线程处于中断状态,tryAcquireNanos中抛出InterruptedException异常,acquire是不会的;acquire中没有Timeout时间限制,在tryAcquireNanos中如果在nanosTimeout时间内未能获得锁,返回false,获取锁流程结束。

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())//中断状态,抛出异常
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);//调用doAcquireNanos,进入队列,去获取锁
    }

doAcquireNanos方法中先检查nanosTimeout不能小于0,入队列,在nanosTimeout时间内去抢锁,超时则不能获取,在获取锁时发生线程中断会抛出异常不能获取锁;

   private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;//截止时间
        final Node node = addWaiter(Node.EXCLUSIVE);//添加到队列尾部
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
               //前节点是head,尝试获取锁
                if (p == head && tryAcquire(arg)) {//如果能获取锁,返回true
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                //在deadline 时间后还未能获取锁,超时发生,返回false
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
               //检查是否处于阻塞,如果nanosTimeout(剩余时间)大于1000纳秒时,
               // 阻塞线程nanosTimeout 个时间
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
           //这里除了发生异常时会执行,有线程中断或者超时发生也会调用, 
           //cancelAcquire说明见acquire
            if (failed)
                cancelAcquire(node);
        }
    }

release(int arg)

先检查tryRelease,确认释放锁,将head节点后继节的线程unpark(唤醒),要注意head节点对应线程正在执行或已经执行完毕,这里是锁在获取后释放所以表示的是已经执行。

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唤醒head节点中未执行的后继节点

   /**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     */
    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.
         */
        //将head节点设置成0,表示已经head节点对应线程已经获取锁完成
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, 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;
        //如果head后继节点是空或取消状态,从tail往前遍历链表,找到最前面且还未执行的节点
        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);
    }

小结

通过tryAcquire方法查询或更新临界资源可用状态,tryRelease将锁状态变更成其他线程可抢夺状态。

竞争情况下,如何保证临界资源访问。
从锁未有线程获取过开始说起,此时CHL队列是空,有线程N,N1,N2,...进入临界区。

  1. 见下图,线程N,其中N第一个发现队列是空且tryAcquire=true,N获得执行权限,不入队列;在锁未释放时,陆续有线程N1,N2...,需要入队列,第一个进入队列的线程N1,会先构造空的pre节点,构造N1节点,然后第二个节线程N2...入下图,等候线程节点是CHL(双向列表),节点的连接通过CAS保证原子操作,pre和tail使用关键字volatile标识具有可见性。
image.png
  1. 线程N未释放锁期间,仍有线程进入获取锁,如上1图中一样,将进入线程CAS进入队列末尾。

3.线程N释放锁,会唤醒head继任节点N1,通过tryAcquire和新进去的线程抢夺锁。如果新线程获得锁,N1阻塞;N1获得锁,变成head节点,新线程进入队列末尾。如下图(橘色着色部分pre和next指向删除):

image.png

4.后续执行流程见步骤2和3,交替执行。

PS:等待队列中线程是可以取消的,如acquireInterruptibly,tryAcquireNanos,发生中断,将中断执行,从队列中删除该节点;当然tryAcquireNanos方法,在nanosTimeout时间内未获得锁,也会从队列中删除。

以上流程,保证了临界资源同一时间只能被一个线程持有,兼且留有扩展可实现公平锁或非公平锁。

共享锁

共享锁实现有acquireShared(int arg),acquireSharedInterruptibly(int arg),tryAcquireSharedNanos(int arg, long nanosTimeout),锁的释放有releaseShared(int arg)。

acquireShared

无锁,或者是S锁时,tryAcquireShared返回值大于0;有X锁时,小于0。

public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
}

doAcquireShared中,先构造SHARED模式node节点入队列(逻辑上和互斥锁的CHL是一样的),如果前节点是head节点,且调用tryAcquireShared大于等于0,获取共享锁成功,将节点设置成head,并释放队列中等待的线程,否则检查节点状态并决定是否阻塞。

private void doAcquireShared(int arg) {
        //上文有说,这里不再介绍
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {//头节点才执行
                    int r = tryAcquireShared(arg);//这里一定要区分共享锁,排他锁
                    if (r >= 0) {
                        //将节点node设置为头结点,释放在队列中等待的线程
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();//如果线程是中断,这里将保持中断
                        failed = false;
                        return;
                    }
                }
               //shouldParkAfterFailedAcquire检查是否应该阻塞
               //parkAndCheckInterrupt阻塞线程,检查线程是否中断态
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)//和acquire实现一样,只有异常时执行,目的是队列中取消节点
                cancelAcquire(node);
        }
    }

设置成头结点,并唤醒处于SIGNAL状态线程,将他们变成PROPAGATE状态。

 private void setHeadAndPropagate(Node node, int propagate) {
       //记录更新前的头结点
        Node h = head; // Record old head for check below
        setHead(node);
        //tryAcquireShare的值大于0,或者原头结点或现头结点是SIGNAL或PROPAGATE态
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            //后继节点是null或是共享锁,执行doReleaseShared
            if (s == null || s.isShared())
               //head不变一个期间,唤醒SIGNAL状态的线程,将状态变更为
                doReleaseShared();
        }
    }

doReleaseShared方法中,开启轮询(终止条件是head节点在一次操作期间不变,head具有可见性,使用h == head可检查):

private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
               //将head状态由SIGNAL变成0
               //CAS成功执行unparkSuccessor唤醒head继任节点的线程
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);//唤醒head继任节点的线程
                }
                //0变成PROPAGATE状态
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

acquireSharedInterruptibly(int arg)

在获取锁前检查线程状态,如果存在中断行为,InterruptedException发生;如果tryAcquireShared小于0,执行doAcquireSharedInterruptibly获取锁:如果获锁成功,唤醒队列中SIGNAL状态的线程;获取锁不成功,检查阻塞状态,决定是否park线程,如果线程发生中断,则InterruptedException发生。

public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

和doAcquireShared方法逻辑上大致相同,不同的在获取锁之前如果发生中断行为,直接抛出InterruptedException异常,将节点从队列中删除。

  /**
     * Acquires in shared interruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

tryAcquireSharedNanos(int arg, long nanosTimeout)

看看下面的代码,是不是很熟悉,简直是复制/粘贴,这里简单介绍下,

  1. 线程有中断行为,不能获取锁,异常InterruptedException发生
  2. tryAcquireShared大于等于0,表示获取锁;
  3. 小于0,有互斥锁在执行,调用doAcquireSharedNanos:

doAcquireSharedNanos在nanosTimeout时间内获取锁,超时则退出,获取锁后逻辑也很熟悉,这里不再介绍;再获取锁过程中如有中断行为发生,异常InterruptedException抛出,节点从队列中删除;

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquireShared(arg) >= 0 ||
            doAcquireSharedNanos(arg, nanosTimeout);
    }
 private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return true;
                    }
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

releaseShared(int arg)

看下代码,releaseShared变更状态成功,执行doReleaseShared(),在head不变一个期间内,唤醒SIGNAL状态的线程,将状态变更为PROPAGATE

 public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

小结下

S表示共享锁,X表示排它锁。共享锁机制表现在存在S、S兼容,X、S不兼容,X、S不兼容,这些行为由实现类的tryAcquireShared(arg)检查正在执行的线程是Share,EXCLUSIVE模式的,在根据state决定是否可以共享还是应该排队等待。

具体实现可以看看Semaphore,CountDownLatch,ReentrantReadWriteLock的readerLock。

实现类

互斥类ReentrantLock,存在S,X锁的ReentrantReadWriteLock,基于共享锁实现的Semaphore和CountDownLatch。

ReentrantLock实现

结构

ReentrantLock实现Lock接口类

public class ReentrantLock implements Lock, java.io.Serializable {

Lock接口类中有如下方法:


image.png

属性

/** Synchronizer providing all implementation mechanics */
private final Sync sync;

//AbstractOwnableSynchronizer内存在
 /**
* The current owner of exclusive mode synchronization.
 */
private transient Thread exclusiveOwnerThread;
image.png

Sync 继承AQS(AbstractQueuedSynchronizer),在内部类中子类有非公平锁NonfairSync、公平锁FairSync。

构造函数

  //默认非公平锁
   public ReentrantLock() {
        sync = new NonfairSync();
    }
   //fair=true,公平锁;false,非公平锁
   public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

lock及unlock等方法如下,具体实现在sync及其实现类NonfairSync、FairSync中。

public void lock() {
     sync.lock();
}

public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
}

 public boolean tryLock() {
       return sync.nonfairTryAcquire(1);
}

public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
 }

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

Sync 实现如下

实现了非公平锁获得,以及锁的释放,以及其他方法。这里AQS中state这里有具体含义,0表示可以获取锁,不等于0表示有线程持有锁。

tryLock() 方法调用这里nonfairTryAcquire,锁的释放调用AQS的release方法,锁的获取NonfairSync,FairSync中。

abstract static class Sync extends AbstractQueuedSynchronizer {
    abstract void lock();
   //尝试获得临界资源的执行权,下称锁(含义同内置锁中持有锁对象,
   //表示只有获得权限的线程在执行,除非释放权限,否则其他线程将等待),
   //这里返回true表示获得锁;false表示有别线程已经获得锁了;
    final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //锁状态由AQS中state表示,具体值由实现类定义
            //在ReentrantLock中state=0表示,无线程占有;state>0表示已经有线程获得执行权
            int c = getState();
            if (c == 0) {
               //CAS更改state的值
                if (compareAndSetState(0, acquires)) {
                    //设置exclusiveOwnerThread属性
                    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;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            //需保证获得state状态的线程和当前线程是一样的
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                //设置null
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

         //当前线程是否获得执行权
        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        // Methods relayed from outer class
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
        //这里指同步状态state的值
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        //如上表述,state>=0,非0表示已经有线程锁定
        final boolean isLocked() {
            return getState() != 0;
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

NonfairSync

如果state状态是0,利用CAS尝试将状态变成1,表示线程获得锁,否则调用AQS#acquire。这样实现了锁的获得不一定按照先进先获取,在进入临界区也可以先获得锁。

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            //这里先争夺锁:利用CAS尝试将state从0变成1,如果成功则当前线程获得执行权;
            //不成功说明有别的线程获得锁,则排队。所以这里不是先进先出这种公平方式来获得锁的
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);//调用AQS#acquire方法进行排队
        }

        protected final boolean tryAcquire(int acquires) {
           //调用Sync的nonfairTryAcquire方法 尝试获得锁,
           //有其他线程持有锁,立即返回false,不排队。
            return nonfairTryAcquire(acquires);
        }
    }

FairSync

锁状态检查及变更由tryAcquire控制,lock方法调用AQS#acquire,实现先进入先获得锁,即是公平锁。

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        //入队,锁执行权限从队列第一开始,到第二个,第三个.....依次获得锁权限
        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        //无线程执行,且队列中无排队线程,CAS将状态变成已被当前线程占有
        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;
        }
    }

ReentrantReadWriteLock

结构

 /** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** Performs all synchronization mechanics */
    final Sync sync;

readlock读锁,即S锁;writerLock写锁,即X锁。Sync 同ReentrantLock的属性Sync 一样,继承AbstractQueuedSynchronizer,有NonfairSync和FairSync。

 static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {
            /* As a heuristic to avoid indefinite writer starvation,
             * block if the thread that momentarily appears to be head
             * of queue, if one exists, is a waiting writer.  This is
             * only a probabilistic effect since a new reader will not
             * block if there is a waiting writer behind other enabled
             * readers that have not yet drained from the queue.
             */
            return apparentlyFirstQueuedIsExclusive();
        }
    }

如果队列中有任务,writerShouldBlock,readerShouldBlock返回true,线程需要进入队列排队执行。

 static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }

AQS中说过,实现类实现tryAcquire,tryRelease,tryAcquireShared,tryReleaseShared(int unused)方法,是使用公平锁还是非公平锁,锁的状态变更。ReentrantReadWriteLock中有ReadLock和WriteLock,如何实现读读兼容,读写不兼容,写读不兼容,看看ryAcquire等实现就可以知道了。

  • WriteLock是互斥锁,锁的获取调用AQS#acquire,acquireInterruptibly,tryAcquireNanos,释放调用release,锁状态的控制由tryAcquire,tryRelease决定,这里要重点介绍。
public static class ReadLock implements Lock, java.io.Serializable {
        public void lock() {
            sync.acquire(1);//AQS#acquire
        }
        public void lockInterruptibly() throws InterruptedException {
            sync.acquireInterruptibly(1);//AQS#acquireInterruptibly
        }
        public boolean tryLock( ) {
            return sync.tryWriteLock();
        }
        public boolean tryLock(long timeout, TimeUnit unit)
                throws InterruptedException {
            //AQS#tryAcquireNanos
            return sync.tryAcquireNanos(1, unit.toNanos(timeout));
       }
        public void unlock() {
            sync.release(1);
        }
。。。
}
  • ReadLock是共享锁,锁的获取调用AQS的acquireShared,acquireSharedInterruptibly,tryAcquireSharedNanos,释放调用releaseShared,锁状态的控制由tryAcquireShared,tryReleaseShared决定,也是这里要重点介绍的。
public static class ReadLock implements Lock, java.io.Serializable {
        public void lock() {
            sync.acquireShared(1);
        }
        public void lockInterruptibly() throws InterruptedException {
            sync.acquireSharedInterruptibly(1);
        }
        public boolean tryLock() {
            return sync.tryReadLock();
        }
        public boolean tryLock(long timeout, TimeUnit unit)
                throws InterruptedException {
            return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
        }
        public void unlock() {
            sync.releaseShared(1);
        }

看看Sync中tryAcquire,tryRelease,tryAcquireShared,tryReleaseShared实现

互斥锁

tryAcquire中同步状态state不等于0表示线程持有,等于0表示可以去获取锁,Walkthrough如下:

  1. 检查同步状态,如果不等于0,意味着有线程持有状态。如果线程是自身线程,支持可重入,state有限制的,在0到(2^16-1)位上,最大值是65535,超过则Error发生,执行 setState(c + acquires)累加,返回true;其他线程,返回false,进入等待队列
  2. state=0,可以去获得锁。writerShouldBlock()=true,也会返回false:非公平锁中返回true,或公平锁中如果等候队列是空链表。如果有竞态线程且CAS成功获得锁,当前线程CAS失败返回false。
  3. 如果当前线程CAS锁状态成功,设置互斥线程为当前线程。
        protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                //w==0表示只有ReadLock存在,无互斥发生
                //w!=0发生互斥,current != getExclusiveOwnerThread() 表示当前线程不是重入的
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

释放锁,tryRelease使用在互斥锁中,如state - releases=0,表示当前线程重入所有地方已经全部执行完毕,需要将exclusiveOwnerThread为null,CAS将锁状态state变更成0,以便于其他线程获得。

        protected final boolean tryRelease(int releases) {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }

共享锁

tryAcquireShared,tryReleaseShared实现。

tryAcquireShared流程如下:

  1. 如果互斥锁被其他线程持有,返回-1,排队等待互斥锁释放;
  2. 如果读锁不被阻塞,共享锁线程数小于65535,CAS更新共享锁计数(高位更新)成功,更新读锁计数;
  3. 1和2外,线程应该被阻塞或更新共享锁状态时因竞争失败,执行fullTryAcquireShared,该方法重复1和2。
        protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            //锁状态
            int c = getState();
            //exclusiveCount确保锁状态最大为65535,
            //这里允许先获得互斥锁,后获得共享锁,如果是其他线程这里返回-1
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            //正在执行共享锁计数,这里使用高位(即2^16位以上)表示
            int r = sharedCount(c);
            //在公平锁,如果队列有元素,readerShouldBlock()=true,
           //表示先进入队列,按照FIFO方式获取锁;非公平锁中,
           //如果有互斥锁存在,不能马上获得锁,readerShouldBlock()=true
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {//2^16~2^32上表示共享锁状态
                if (r == 0) {//设置第一个执行的线程
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;//重入线程,计数加1
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;//统计持有共享锁数量
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }

释放锁,锁状态减SHARED_UNIT,共享锁计数减1。

 protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    // Releasing the read lock has no effect on readers,
                    // but it may allow waiting writers to proceed if
                    // both read and write locks are now free.
                    return nextc == 0;
            }
        }

ReentrantReadWriteLock小结

锁state低位0~(216-1)上表示互斥状态(只有1个线程占有,同一个线程可重复,state将acquires累加);216-2^32上表示共享锁行为,acquires累加(可以是不同线程,自身线程可重入)。

WriteLock基于互斥实现,也就是AQS#acquire,release实现,有readLock或其他线程获得锁会排队等候释放锁,tryAcquire中确保获取锁时只有state=0,CAS从0变成acquires才能获得锁,当然自身线程是已经获得互斥锁,再次重入也没问题。

ReadLock共享锁,基于AQS#acquireShared,releaseShared实现,有WriteLock存在时需要排队等候互斥锁的释放才能抢锁,支持重入。

如果线程先WriteLock#lock,在ReadLock#lock,是可以的,执行顺序上WriteLock#lock->ReadLock#lock ->ReadLock#unLock ->WriteLock#unLock。这种行为被人称为锁降级

先ReadLock#lock,在WriteLock#lock,会发生什么?从tryAcquire代码中可以看到,只有state等于0且CAS成功获得锁或者自身已经获得互斥锁,程序才能继续往下执行,但自身线程ReadLock#lock获得锁,此时state!=0,WriteLock#lock会将线程进入等候队列等候state=0,WriteLock一直等不到state变成0饿死,程序无法继续执行。这种行为也就是所谓的锁升级

Semaphore实现

信号量,实际使用场景还挺多,比如限流中并发数限制场景。

设置许可数量permits,通过以下方法操作

acquire()
内部实现为AQS#acquireSharedInterruptibly。获取许可,permits-1;如果permits=0时,acquire方法会将调用线程排队进入等待。

acquire(int permits)
同acquire(),获得许可前提是总许可数量>参数permits

release()
程序执行完成后,释放许可,许可数量=permits+1

release(int permits) 
逻辑同release(),许可数量=总许可数量+参数permits

核心为tryAcquireShared实现,调用Semaphore(int permits) 初始化时即是将锁state设置成permits。
看下公平和非公平下实现

nonfairTryAcquireShared,非公平下,CAS在同步状态state大于acquires,设置state=state-acquires成功,不管线程是先进入还是后进入的即是获得许可。

final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

公平下,hasQueuedPredecessors()=true表示等候队列有线程节点,需入队尾,保证是按照最先进来的线程先获得许可。

protected int tryAcquireShared(int acquires) {
            for (;;) {
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

CountDownLatch实现

闭锁,含义是等待一件事或一堆事完成后才去做,如WSRS中,和目的端通信,可重试3次直到收到应答,如果3次没有收到应答或者超过3秒,就会到其他流程,如下。

重试3次流程:
CountDownLatch nums = new CountDownLatch(3);
do{
    Thread t = new Thread (new T1());
     t.start();
     t.join(1000);
}while(通信成功 || nums.await(3,TimeUnit.SECONDS))
其他流程

通信线程:
T1 implements Runnable{
public void run(){
   和endPoint通信
     if(通信失败){
         nums.countDown();
     }
 }
}

CountDownLatch方法:

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    public void countDown() {
        sync.releaseShared(1);
    }

    public long getCount() {
        return sync.getCount();
    }

可以看出基于AQS#acquireSharedInterruptibly,releaseShared,tryAcquireSharedNanos方法,是S锁。

核心还是在CountDownLatch#Sync上,定义了tryAcquireShared和tryReleaseShared实现。

下面代码,业务方调用CountDownLatch#await()时,如果锁状态state(也就是Sync中count)等于0,意味着业务方获得S锁可以执行业务代码,state不等于0,业务方线程排队等待state=0或超时。

CountDownLatch#countDown()将使state=state-1,直到state=0。

private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

AQS与synchronized相比

AQS是编程式锁,与synchronized区别还是很大的:

  1. 原理:synchronized基于JVM原语;AQS编程式锁,双向链表+同步状态+CAS+LockSupport实现;
  2. 使用上,synchronized使用比较简明,使用关键字synchronized锁定资源和{}锁定执行范围;AQS,要对临界资源和范围显示加锁和解锁,缺一不可,使用上稍稍复杂,但胜在灵活可控;
  3. 特性上,synchronized只能支持非公平锁,不支持公平锁,对等待线程无法中断执行和设置超时时间;相反,AQS能实现公平锁和非公平锁,也支持中断等待线程,以及设置超时时间去获取锁。需要注意的是获得锁的线程,AQS同synchronized一样的,无法取消执行的
  4. 负载上:synchronized在1.5,1.6以来做了很多优化,诸如:无锁化,锁粗化,锁自旋,轻量级锁,偏向锁等,性能上和AQS相差无几;但在并发冲突高场景下,synchronized抢锁频繁,CPU消耗大波动大,AQS表现平稳。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容