并发之父 :Doug Lea
Java并发编程核心在于java.concurrent.util包而juc当中的大多数同步器 实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获 取等,而这个行为的抽象就是基于AbstractQueuedSynchronizer简称AQS,AQS定 义了一套多线程访问共享资源的同步器框架,是一个依赖状态(state)的同步器。
AQS具备特性
•阻塞等待队列 •共享/独占 •公平/非公平 •可重入 •允许中断
例如Java.concurrent.util当中同步器的实现如Lock,Latch,Barrier等,都是基 于AQS框架实现
一般通过定义内部类Sync继承AQS将同步器所有调用都映射到Sync对应的方法
AQS内部维护属性volatile int state (32位)
state表示资源的可用状态
State三种访问方式
getState()、setState()、compareAndSetState()
AQS定义两种资源共享方式
Exclusive-独占,只有一个线程能执行,如ReentrantLock
Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch
AQS定义两种队列
同步等待队列
条件等待队列
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只 需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护 (如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器 实现时主要实现以下几种方法:
isHeldExclusively():该线程是否正在独占资源。只有用到 condition才需要去实现它。
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败 则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败 则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败; 0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许 唤醒后续等待结点返回true,否则返回false。 同步等待队列
AQS当中的同步等待队列也称CLH队列,CLH队列是Craig、Landin、 Hagersten三人发明的一种基于双向链表数据结构的队列,是FIFO先入先出线程等待队列,Java中的CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制(队列中的线程自旋是不释放cpu的使用权,阻塞的话,会保存状态,释放资源,唤醒会造成线程的上下文切换;上下文切换又可以理解为资源的申请和释放)。
源码分析:
首先ReentrantLock的创建:
1.默认:
public ReentrantLock() {
sync = new NonfairSync(); //非公平锁
}
2.带参数:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync(); //true是公平锁,false是非公平锁
}
公平锁:在唤醒队列中等待线程的时候只会唤醒队列头部的一个阻塞线程,按部就班,有秩序
非公平锁:在唤醒队列等待线程的时候有可能刚加入的线程会和头部刚唤醒的队列形成竞争,俗称插队。
源码:
公平锁:
final void lock() {
acquire(1); //尝试获取锁,失败就添加进入队列
}
非公平锁:
final void lock() {
if (compareAndSetState(0, 1)) //直接cas锁状态,失败了再进行公平锁的操作
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
阻塞线程:jdk.internal.misc.Unsafe.park(boolean, long)
唤醒线程:jdk.internal.misc.Unsafe.unpark(Object)
AQS类的属性:
state(表示当前线程被上锁的次数,如果重复上锁,则state会累加,上多少锁就要解多少锁)
head 队列的头部,只在sethead方法被修改,head如果存在的话,就要保证waitStatus不被取消
tail 队列的尾部,只在enq方法中用到,以添加新的等待线程节点
AQS队列
其实元素是一个Node对象,Node是一个双向链表结构,内部有属性:
prev(前一个节点)、next(下一个节点)、nextWaiter(条件队列中下一个等待节点)
waitStatus(当前节点是出于什么样的状态下):
SIGNAL 可唤醒的
CANCELLED 由于超时或中断,已经被取消
CONDITION 条件队列下的节点
PROPAGATE 传播:共享模式下的节点,无条件的传播下去
头部和尾部默认初始化是头等于尾:
java.util.concurrent.locks.AbstractQueuedSynchronizer.enq(Node)
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node())) //头部初始化
tail = head; //尾部初始化
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}