这篇文章首先对AQS进行详细解析,作为上文,还有下文,专门针对几个以AQS为低层进行实现的工具类进行讲解,通过这篇文章,能够对AQS有更好的理解。
AbstractQueuedSynchronizer(AQS)是java并发包中很多并发工具类的基础,比如java.util.concurrent.locks.ReentrantLock
重入锁、java.util.concurrent.locks.ReentrantReadWriteLock
读写锁、java.util.concurrent.Semaphore
信号量、java.util.concurrent.CountDownLatch
。它们都是在自己的类中又定义了内部类Sync类继承AbstractQueuedSynchronizer,然后重写其中的一些protected修饰的方法。AQS内部定义了一些模板方法,这些模板方法会调用前面说到的protected修饰的方法。通过模板方法设计模式,所有的这些高级的同步组件都不需要再重写关于线程排队、等待、唤醒等相关代码,这些代码被AQS抽离出来实现了重用。
下面我们从独占锁ReentrantLock入手,来看看AQS内部到底做了哪些事情。首先是一段很简单的代码,实现了三个线程同时调用被ReentrantLock加锁保护的方法sayHello。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class AQSDebug {
private Lock lock = new ReentrantLock();
private void sayHello() {
lock.lock();
System.out.println("hello");
lock.unlock();
}
public static void main(String[] args) {
AQSDebug aqsDebug = new AQSDebug();
new Thread(aqsDebug::sayHello, "first").start();
new Thread(aqsDebug::sayHello, "second").start();
new Thread(aqsDebug::sayHello, "third").start();
}
}
为了实现三个线程相互抢占的效果,我们使用多线程断点调试的方法(参考这篇文章),让线程first先获取到锁,然后切换到线程second或者third进行调试,这样就能够看到因为获取不到锁,而加入同步队列,等待,被唤醒的过程。
切换到线程second,我们来看一下,会走到java.util.concurrent.locks.ReentrantLock.NonfairSync#lock
方法,NonfairSync继承了Sync,Sync继承了AbstractQueuedSynchronizer。在lock方法中,首先尝试用CAS的方式,将AQS中用来表示同步状态的volatile变量state设置为1,这里用CAS的方式是保证原子性,因为可能有多个线程同时来争抢。
static final<