AQS(Abstract Queued Synchronizer)是一个抽象的队列同步器,通过维护一个共享资源状态(Volatile Int State)和一个先进先出(FIFO)的线程等待队列来实现一个多线程访问共享资源的同步框架。
AQS原理
AQS为每个共享资源都设置一个共享资源锁,线程在需要访问共享资源时首先需要获取共享资源锁,如果获取到了共享资源锁,便可以在当前线程中使用该共享资源,如果获取不到,则将该线程放入线程等待队列,等待下一次的资源调度,许多同步类的实现都依赖于AQS,例如常用的ReentrantLock、Semaphore、CountDownLatch。
state:状态
Abstract Queued Sychronizer维护了volatile int类型的变量,用于表示当前的同步状态。volatile虽然不能保证操作的原子性,但是能保证当前变量state的可见性。
state的访问方式有三种:getState()、setState()和compareAndSetState(),均是原子操作,其中,compareAndSetState的实现依赖于Unsafe的compareAndSwaplnt()。
AQS共享资源的方式:独占式(Exclusive)和共享式(Share)
独占式:只有一个线程能执行,具体的Java实现有ReentrantLock
共享式:多个线程可以同时执行,具体的Java实现有Semphore和CountDownLatch。
AQS只是一个框架,只定义了一个接口,具体资源的获取、释放都由自定义同步器去实现。不同的自定义同步器争用共享资源的方式也不相同,自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护,如获取资源失败入队、唤醒出队等,AQS已经在顶层实现好,不需要具体的同步器再做处理。
自定义同步器的主要方法如下图所示:
ReentrantLock对AQS的独占方式实现为:ReentrantLock中的State初始值为0表示无锁状态。在线程执行tryAcquire()获取该锁后ReentrantLock中的state+1,这时该线程独占ReentrantLock锁,其他线程再通过tryAcquire()获取锁均会失败,直到该线程释放锁后state再次为0,其他线程才有机会获得该锁。该线程在释放锁之前可以重复获取该锁,每获取一次便会执行一次state+1,因此ReentrantLock也属于可重入锁。但获取多少次锁就需要释放多少次锁,这样才能保证state最终为0。如果获取锁的次数多余释放锁的次数,则会出现该线程一直持有该锁的情况;如果获取锁的次数少于释放锁的次数,则运行中的程序会报锁异常。
一般来说,自定义同步器要么采用独占方式,要么采用共享方式,实现类只需要实现tryAcquire、tryRelease和tryAcquireShared、tryReleaseShared中的一组即可。但是AQS也支持自定义同步器同时实现独占和共享两种方式,例如ReentrantReadWriteLock在读时采用了共享方式,在写入时采用了独占方式。