# AQS
队列同步器AbstractQueuedSynchronizer,用来构建锁或者其他同步组件的基础框架,内部使用一个变量state来表示同步状态,同时使用一个FIFO队列来完成线程的排队工作。
锁或者其他同步组件一般都会定义一个静态内部类,该静态内部类会继承AQS,同时**重写**AQS中的方法,重写AQS中的方法时需要用到下面三个方法来获取同步状态。
- **getState()**
获取state属性的内容。
- **setState(int newState)**
设置state属性的内容。
- **compareAndSetState(int expect, int update)**
使用CAS设置当前状态,保证状态设置的原子性。
> 总结:如何自定义一个锁或者同步组件?
> 创建静态内部类继承AQS,重写AQS中的**可重写**的方法,在里面使用AQS提供的如上三个方法来获取、修改同步状态。最后调用AQS中的模板方法来进行操作,模板方法中会调用重写的方法。
> **即使用者调用模板方法,模板方法调用重写方法,重写方法调用如上三个方法。**
:-: ![](https://img.kancloud.cn/ec/d8/ecd87535e99d81e7fc1de29eb7bf6e84_1092x589.png)
## 可重写的方法
1.
```
protected boolean tryAcquire(int arg);
```
**独占式获取**同步状态,查询当前状态并根据具体条件设置同步状态。
2.
```
protected boolean tryRelease(int arg);
```
**独占式释放**同步状态,等待的线程有机会获取同步状态。
3.
```
protected int tryAcquireShared(int arg);
```
**共享式获取**同步状态,返回大于等于0的值表示获取成功,反之获取失败。
4.
```
protected boolean tryReleaseShared(int arg);
```
**共享式释放**同步状态。
5.
```
protected boolean isHeldExclusively();
```
表示是否被当前线程占用。
## 模板方法
1. 独占式获取同步状态
```
void acquire(int arg);
```
当前线程获取成功则会返回,否则进入同步队列**等待**,调用重写方法中的**tryAcquire**。
2. 独占式获取同步状态,响应中断
```
void acquireInterruptible(int arg);
```
如果当前线程被中断,则会抛出InterruptedException。
3. 超时获取同步状态
```
boolean tryAcquireNanos(int arg, long nanos);
```
在acquireInterruptible的基础上设置超时时间,如果超时时间还没有获取到同步状态,会返回false,否则返回true。
4. 共享获取同步状态
```
void acquireShared(int arg);
```
5. 共享获取同步状态,响应中断
```
void acquireSharedInterruptible(int arg);
```
6. 共享获取同步状态,响应中断,添加超时时间
```
boolean tryAcquireSharedNanos(int arg, long nanos);
```
7. 独占式释放同步状态
```
boolean release(int arg);
```
同步队列中的第一个节点将会被唤醒。
8. 共享式释放同步状态
```
boolean releaseShared(int arg);
```
9. 获取等待在同步队列上的线程集合
```
Collection<Thread> getQueuedThreads();
```
总之:模板可以分为三类:独占式获取与释放同步状态、共享式获取与释放同步状态、查询同步队列线程等待情况。获取又有分为`中断`、`超时`。
## 自定义同步组件
~~~
/**
* 自定义不可重入锁
*/
public class UnReetrantLock implements Lock {
// 同步锁
public static class Sync extends AbstractQueuedSynchronizer {
/**
* 尝试独占式获取锁
*
* @param arg
* @return
*/
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) { // 调用CAS,当state为0时获取成功,底层由C++提供
// 加上锁了
// 设置持有锁的线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/**
* 尝试释放锁
*
* @param arg
* @return
*/
@Override
protected boolean tryRelease(int arg) {
// 不会有其他线程竞争、直接设置就行了
setExclusiveOwnerThread(null);
setState(0); // 保证前面的内容对其他线程可见
return false;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
public Condition newCondition() {
return new ConditionObject();
}
}
private Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
~~~
## AQS实现
**底层数据结构:同步队列**
AQS中使用一个双向链表来保存等待同步状态的线程,链表的节点用其内部自定义的Node表示,Node类源码:
~~~
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
// 同步状态
volatile int waitStatus;
volatile Node prev;
volatile Node next;
// 线程引用
volatile Thread thread;
Node nextWaiter;
}
~~~
waitStatus有五个状态:
- cancelled = 1:同步队列中的线程等待超时或者中断时的状态,后续不会再改变。
- signal = -1:节点获取同步状态,一般是队头节点,后续节点处于等待状态。
- condition = -2:节点在等待队列中(注意不是同步队列),线程等待Condition,当Condition调用了signal()之后,该节点会从**等待队列**转移到**同步队列**。
- propagate = -3:
- initial = 0:初始状态。
同步队列采用尾插法的方式,同时会使用CAS保证尾插的时候是线程安全的。其结构如下:
:-: ![](https://img.kancloud.cn/88/ff/88ff8ad5fad5ceddd726eac9d92662b6_892x350.png)
其中队头是获取同步状态成功的节点,当首节点的线程释放同步状态的时候,会唤醒后继的节点,后继节点会成为首节点。(这个过程不用CAS,没有竞争的情况。)
**acquire方法流程**
~~~
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
~~~
:-: ![](https://img.kancloud.cn/86/0e/860eee5226ec9a94eedf5c3a9d220312_887x722.png)
同步队列中的节点不断的在自旋判断其**前驱节点是不是头节点**,如果是则尝试获取同步状态,否则会阻塞节点中的线程。
**acquireShared方法流程**
~~~
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
~~~
:-: ![](https://img.kancloud.cn/4d/d8/4dd803aff2d53d8b8b27f923533ba95e_925x699.png)
## ReetantLock
ReentrantLock,支持重入锁和公平与非公平锁。
### ReentrantLock实现可重入
重入锁:支持线程反复的获取锁资源而不会自己阻塞自己,有两个问题要实现:
1. 线程再次获取锁,判断是否是当前线程获取锁。
2. 锁的最终释放,需要计数锁被重入几次,计数器最终释放为0时才表示锁的最终释放。
:-: ![](https://img.kancloud.cn/f0/c6/f0c68a8e0dde268ff6346d568ba131bf_932x506.png)
例如非公平锁每次再尝试获取锁的时候都会判断是不是同个线程,如果是的话增加计数器的值。释放锁时等到计数器的值为0时才将占有锁的线程设置为null。
:-: ![](https://img.kancloud.cn/ff/fd/fffd95b0ff268f37137bdb4768104bc2_920x363.png)
### 公平锁与非公平锁
公平锁:获取锁的线程按照绝对的时间顺序,FIFO。
非公平锁:只要CAS设置同步状态成功,就获取锁,不会按照FIFO顺序。
ReentrantLock的构造方法中传入true时可以创建公平锁:
~~~
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
~~~
公平锁在tryAcquire的时候会判断当前线程是否有前驱节点,有的话则会等待前驱节点释放之后在获取尝试获取锁。
公平锁的tryAcquire:
:-: ![](https://img.kancloud.cn/24/e2/24e231a5f7ab6f03340aa1a89c823e8d_978x611.png)
hasQueuePredecessors方法用来判断是否有前驱节点
非公平锁的tryAcquire:
:-: ![](https://img.kancloud.cn/20/ce/20ced0fe4146742378f9716402b714a7_868x514.png)
> 问:如何实现公平锁?
> 构造函数的参数传入true,在重写的tryAcquire方法中判断当前线程是否有前驱线程,有的话尝试获取同步状态失败,以此来达到公平的效果。
对比:
公平锁虽然会按照FIFO原则,但是会进行大量的线程切换,非公平锁虽然可能会造成其他线程饥饿,但是可以极大提高吞吐量。
:-: ![](https://img.kancloud.cn/a2/a4/a2a482a54328499aab8f34ae642e3f8f_1452x306.png)
- 第一章 Java基础
- ThreadLocal
- Java异常体系
- Java集合框架
- List接口及其实现类
- Queue接口及其实现类
- Set接口及其实现类
- Map接口及其实现类
- JDK1.8新特性
- Lambda表达式
- 常用函数式接口
- stream流
- 面试
- 第二章 Java虚拟机
- 第一节、运行时数据区
- 第二节、垃圾回收
- 第三节、类加载机制
- 第四节、类文件与字节码指令
- 第五节、语法糖
- 第六节、运行期优化
- 面试常见问题
- 第三章 并发编程
- 第一节、Java中的线程
- 第二节、Java中的锁
- 第三节、线程池
- 第四节、并发工具类
- AQS
- 第四章 网络编程
- WebSocket协议
- Netty
- Netty入门
- Netty-自定义协议
- 面试题
- IO
- 网络IO模型
- 第五章 操作系统
- IO
- 文件系统的相关概念
- Java几种文件读写方式性能对比
- Socket
- 内存管理
- 进程、线程、协程
- IO模型的演化过程
- 第六章 计算机网络
- 第七章 消息队列
- RabbitMQ
- 第八章 开发框架
- Spring
- Spring事务
- Spring MVC
- Spring Boot
- Mybatis
- Mybatis-Plus
- Shiro
- 第九章 数据库
- Mysql
- Mysql中的索引
- Mysql中的锁
- 面试常见问题
- Mysql中的日志
- InnoDB存储引擎
- 事务
- Redis
- redis的数据类型
- redis数据结构
- Redis主从复制
- 哨兵模式
- 面试题
- Spring Boot整合Lettuce+Redisson实现布隆过滤器
- 集群
- Redis网络IO模型
- 第十章 设计模式
- 设计模式-七大原则
- 设计模式-单例模式
- 设计模式-备忘录模式
- 设计模式-原型模式
- 设计模式-责任链模式
- 设计模式-过滤模式
- 设计模式-观察者模式
- 设计模式-工厂方法模式
- 设计模式-抽象工厂模式
- 设计模式-代理模式
- 第十一章 后端开发常用工具、库
- Docker
- Docker安装Mysql
- 第十二章 中间件
- ZooKeeper