几种基于AQS的同步器(ReentrantLock,Semaphore,CountDownLatch)源码解析

前言

本文主要介绍几种基于AQS实现的同步器,阅读本文前强烈建议先看一下博主之前的关于AQS源码介绍的文章【AbstractQueuedSynchronizer源码解析】,可以帮助各位对各种同步器实现的细节有更加深刻的理解,本文旨在介绍AQS各个子类针对于tryAcquire, tryRelease, tryAcquireShared, tryReleaseShared, isHeldExclusively的。
因为AQS是一个抽象类,所以当我们基于它来实现同步器时需要实现它里面的几个方法:

Method NameDescription
protected boolean tryAcquire(arg)尝试以独占模式获取同步,arg为获取同步的次数
protected boolean tryRelease(arg)尝试释放独占模式下的同步,arg为释放同步的次数
protected int tryAcquireShared(arg)尝试在共享模式下获取同步,arg为尝试获取同步的次数:<0 : 获取失败;=0:获取成功但无资源可用;>0:获取成功且有资源可用
protected booealn tryReleaseShared (arg)尝试在共享模式下释放资源
protected int isHeldExclusively该线程是否属于独占线程,只有当用到Condition对象时需要实现它

一般来说,同步器会基于独占或共享中的一种来实现,但是也有基于独占和共享中的两种来实现的,比如ReentrantReadWriteLock。

ReentrantLock

ReentrantLock介绍

  • 可重入互斥锁与synchronize方法和生命使用的隐式监视器锁有着相同的行为和语义,但是可重入锁在此基础上进行了一定的扩展。
  • 可重入锁是被上一个成功上锁单尚未释放的线程所持有。当一个线程调用lock()方法尝试对一个资源上锁时,如果该资源尚未被其他线程所持有,则该lock()方法会成功获取锁并返回。当该资源已经被其他线程所持有,该方法会立马返回。资源是否被该线程所持有的状态可以通过isHeldByCurrentThread()和getHoldCount()方法来查看。
  • 可重入锁有2个构造函数,一个无参构造函数ReentrantLock()和带boolean值构造函数ReentrantLock(boolean fair):构造函数主要是用来选择生成公平锁还是非公平锁,当我们选择无参构造函数时,ReentrantLock默认选择通过非公平锁来实现。
  • ReentrantLock的最大特点是当一个线程已经持有锁后,当他再次来尝试上锁时不需要排队,只会将state状态+1。当进行1次释放时,会将state状态-1,只有当state状态为0时,可重入锁才会被释放。
  • 同一个线程最多可以重入2147483647次,超过这个次数该方法会抛出异常。

由于ReentrantLock是独占锁,所以它只需要实现AQS中的tryAcquire和TryRelease即可。
ReentrantLock由公平锁和非公平锁两种,下面我们来研究一下他ReentrantLock的内部类结构:
ReentrantLock内部类图
从上图我们可以很清楚的看出,无论是公平锁(FairSync)还是非公平锁(FairSync)都是Sync的子类,而Sync类又继承了AQS。
因为AQS博主在之前一篇文章中已经介绍过,所以在这里我们只需要重点研究一下NofairSync和FairSync两个内部类以及其父类Sync。

   /**
     *Sync
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
        abstract void lock();

        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        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;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                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();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 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
        }
    }
    /**
     *
     * 非公平锁
     */
    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尝试进行加锁
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            //加锁失败再调用AQS的acquire方法进行排队加锁
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

非公平锁:

lock()方法先直接尝试通过CAS进行加锁。如果成功则上锁成功,如果失败则调用AQS中的acquire方法尝试进行加锁。

tryAcquire方法直接调用自己的父类Sync中的nofairTryAcquire方法.

nofairTryAcuiqre方法会先通过他的父类AQS中的getState()方法当前资源是否被其他线程占用,
1. 如果线程占用,尝试通过CAS进行加锁,加锁成功的话,获取资源,最后返回true。
2. 如果有线程占用,判断是否该线程为它自己,如果是标识当前占有资源数的数值state+1,然后返回true;
3. 如果有线程占用,并且该线程不为他自己,加锁失败,返回false;

    /**
     * 公平锁
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

		//直接调用AQS的acquire方法进行排队加锁
        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //如果当前资源没有线程持有
            if (c == 0) {
            	//如果当前没有排队的队列 && 尝试进行CAS加锁成功
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果当前资源有线程持有且该线程为它自己
            else if (current == getExclusiveOwnerThread()) {
                //重入数量+1
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

公平锁:

从上面的代码中,我们可以很容易的发现,公平锁的加锁过程和非公平锁基本一样,除了一点:公平锁在加锁之前会先判断当前AQS队列中是否有其他节点在排队,如果有直接进入队尾排队。只有当当前不存在AQS队列或者不存在排队中的节点时,它才会去尝试加锁。

释放锁的过程

公平锁和非公平锁释放锁的过程都是相同的,调用Sync中的tryRelease方法:

  1. 先将当前state数减去释放的资源数得到的值赋予c存储起来
  2. 再判断资源是否为自己持有,如果是再判断
  3. 判断c是否为0
    a. 如果是,将该资源所属的线程置为空,最后将状态state置为0,返回true
    b. 如果不是,将状态state的值置为c,返回false

Semaphore

Semaphore,计数信号量。
从概念上来说,一个semaphore维护了一组permits。每个请求会先尝试获取指定指定数目的permit,如果获取失败会被阻塞,直到获取成功。每次释放,会增加指定数目的permit回到permits中。虽然理论是这样,但是实际上,代码中并没有permit对象被使用,semaphore只是保持可用数量的技术并根据这个数量采取相应的操作。
Semaphore是一个共享资源锁。它常常被用来限制同一时刻内可以访问资源的线程数目。

相比于ReentrantLock,Semaphore的结构相对简单,以下是Semaphore的主要结构类图:
Semaphore内部结构图

   /**
     * semaphore 同步.  
     * 使用 AQS state来表示permits
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;

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

        final int getPermits() {
            return getState();
        }

		/** 
		 * 非公平的方法获取同步
		 */
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                 // 当前可用的-尝试获取的 = remaining
                int remaining = available - acquires;
                //如果 remaining <  0 直接返回剩余数
        		//如果 remaining >= 0 进行CAS并返回剩余数 
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

		/**
		 * 释放同步
		 */
        protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                //当前剩余的+释放的 = next
                int next = current + releases;
                //如果next比释放之前的值还要小,说明overflow了,抛出异常
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                // 如果CAS释放同步成功,如果成功返回true
                if (compareAndSetState(current, next))
                    return true;
            }
        }

		/**
		 * 减少premits的数量
		 */
        final void reducePermits(int reductions) {
            for (;;) {
                int current = getState();
                //当前剩余的-想要减少的 = next
                int next = current - reductions;
                 //如果next比释放之前的值还要大,说明underflow了,抛出异常
                if (next > current) // underflow
                    throw new Error("Permit count underflow");
                //进行CAS减少permits数量,如果成功返回true
                if (compareAndSetState(current, next))
                    return;
            }
        }
		
	    /**
		 * 获取并返回当前所有可用的permits
		 */
        final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }
    }

非公平锁与公平锁

公平锁和非公平锁的版本区别仅仅在于:公平锁会在尝试获取permits之前用AQS中的hasQueuedPredecessors()方法判断他之前是否已经有线程在排队了,如果有直接fan

    /**
     * NonFair version
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -2694183684443567898L;

        NonfairSync(int permits) {
            super(permits);
        }

        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }
    /**
     * Fair version
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = 2014338818796000944L;

        FairSync(int permits) {
            super(permits);
        }

        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

CountDownLatch用于让一个或多个线程一直处于等待状态直到其他线程的一组操作被执行完毕。

CountDownLatch内部结构图

Sync

    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;
            }
        }
    }

countDown() && await()

 /**
     * 让当前线程在latch降到0之前一直等待,除非线程被打断。
     * 当当前计数为0是,这个方法会立刻返回。
     */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    /**
     * 
     * Decrements the count of the latch, releasing all waiting threads if
     * the count reaches zero.
     * 减少latch的计数,当计数为0时,释放所有的处于等待的线程。
     *
     */
    public void countDown() {
        sync.releaseShared(1);
    }

小结

本文介绍了几种常见的基于AQS的同步器,当然还有很多juc包下的同步器没有介绍到,比如与ReentrantLock很相似但同时实现了共享模式和独占模式的ReentrantReadWriteLock,与CountDownLatch相像的CycleBarrier等等,有机会我将在之后再一一解析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值