1. 什么是AQS
AQS - AbstractQueuedSynchronizer
AQS定义了实现线程同步器的基础框架, 线程同步器的作用是协调多个线程对共享资源的访问。
AbstractQueuedSynchronizer派生出Java并发包的重要类, 如下:
- CountDownLatch(倒计时同步器)
- CyclicBarrier(循环栅栏同步器)
- Semaphore(信号量同步器) [ˈseməfɔ:(r)]
- ReentrantLock(可重入锁)
在多个线程共享同一个资源时, 可以基于AQS的相关实现类来实现多线程对该资源的同步访问,
这样可以避免多线程并发访问导致的数据不一致问题。
2. AQS基础
由于AQS里面用到了大量的CAS, 因此学习AQS之前我们还是有必要简单的先了解下CAS、公平锁和非公平锁。
CAS
CAS 全称是 compare and swap,是一种用于在多线程环境下实现同步功能的机制。 CAS 操作包含三个操作数: 内存位置、预期数值和新值。 CAS 的实现逻辑是将内存位置处的数值与预期数值相比较 - 若相等 --> 则将内存位置处的值替换为新值。 - 若不相等 --> 则不做任何操作,这个操作是个原子性操作, java里面的AtomicInteger等类都是通过cas来实现的。
公平锁(先到先得)
先到先得:
多个线程按照申请锁的顺序去获得锁, 线程会直接进入队列去排队, 队列中第一个才能获得到锁。
优点:
等待锁的线程不会饿死, 每个线程都可以获取到锁。
缺点:
整体吞吐效率相对非公平锁要低
等待队列中除第一个线程以外的所有线程都会阻塞
CPU唤醒阻塞线程的开销比非公平锁大
非公平锁(弱肉强食)
多个线程去获取锁的时候, 会直接去尝试获取,
获取不到, 再去进入等待队列,
如果能获取到,就直接获取到锁。
优点:
可以减少CPU唤醒线程的开销
整体的吞吐效率会高点
CPU也不必唤醒所有线程, 减少了唤起线程的数量
缺点:
处于等待队列中的线程有可能要等很久才会获得锁, 可能会饿死。
举个例子比喻一下:
比如我们去食堂就餐的时候都要排队, 大家都按照先来后到的顺序排队打饭,这就是公平锁。
如果等到你准备拿盘子打饭的时候, 直接蹦出了一个五大三粗的胖子插队到你前面,
你看打不赢他只能忍气吞声让他插队,等胖子打完饭了又来个小个子也来插你队,
这时候你没法忍了, 直接大吼一声让他滚, 这个小个子只能屁颠屁颠到队尾去排队了这就是非公平锁。
3. AQS源码分析
AQS的基本属性:
// 头节点 private transient volatile Node head; // 阻塞的尾节点,每个新的节点进来,都插入到最后,也就形成了一个链表 private transient volatile Node tail; // 这个是最重要的,代表当前锁的状态,0代表没有被占用,大于 0 代表有线程持有当前锁 // 这个值可以大于 1,是因为锁可以重入,每次重入都加上 1 private volatile int state; // 代表当前持有独占锁的线程,举个最重要的使用例子,因为锁可以重入 // reentrantLock.lock()可以嵌套调用多次,所以每次用这个来判断当前线程是否已经拥有了锁 // if (currentThread == getExclusiveOwnerThread()) {state++} private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer