AQS构建锁和同步器的框架
共享资源空闲时,分配给当前请求线程,并锁定资源。
不空闲时,请求线程加入CLH队列(FIFO,CAS修改同步状态),其能够实现线程阻塞以及唤醒功能。 AQS对资源共享的方式: Exclusive(独占)
只有一个线程能执行,如RenntrantLock。分为公平锁(按照队列中的顺序)和非公平锁(抢到就是谁的) Share(共享)
如Semaphore
1.继承AbstractQueuedSynchronizer并重写指定的方法。
2.将AQS组合在自定义同步组件实现,并调用模板方法,模板方法会调用使用者重写方法 需要重写的方法: isHeldExclusively() 线程是否在独占资源 tryAcquire(int) 独占方法,尝试获取资源
tryRelease(int) 独占资源,尝试释放资源
tryAcquireShared(int) 共享资源,尝试获取资源
tryReleaseShared(int) 共享资源,尝试释放资源
Semaphore(信号量)-允许多个线程同时访问
CountDownLatch(倒计时)协调多个进程之间的同步。控制线程等待,直到某一线程结束
CyclicBarrier(循环栅栏)让一组线程达到屏障时被阻塞,直到最后一个线程达到屏障是栅栏才会打开
tryAcquire()尝试直接去获取资源,如果成功则直接返回(这里体现了非公平锁,每个线程获取锁时会尝试直接抢占加塞一次,而CLH队列中可能还有别的线程在等待);
addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
acquireQueued()使线程阻塞在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
tryAcquire(int) tryAcquire方法是为了去重写方法
此方法尝试去获取独占资源。如果获取成功,则直接返回true,否则直接返回false。这也正是tryLock()的语义,还是那句话,当然不仅仅只限于tryLock()。如下是tryAcquire()的源码:
1 protected boolean tryAcquire(int arg) { 2 throw new
UnsupportedOperationException(); 3 }
模板方法 独享模式下 一个线程同一时间内只允许获取一把锁
钩子方法
通过acquire和acquireShared获取锁,通过release和releaseShared释放锁
共享式获取与独占式获取最大的区别就是同一时刻能否有多个线程同时获取到同步状态。
共享式访问资源时,同一时刻其他共享式的访问会被允许。独占式访问资源时,同一时刻其他访问均被阻塞。
AQS都提供了子类实现的钩子方法,独占式的代表方法有:tryAcquire和tryRelease以及isHeldExclusively方法,共享式的代表方法有:tryAcquireShared和tryReleaseShared方法。
AQS中获取操作和释放操作的标准形式: boolean acquire() throws InterruptedException{
while( 当前状态不允许获取操作 ){
if( 需要阻塞获取请求){
如果当前线程不在队列中,则将其插入队列
阻塞当前线程
}else{
返回失败
}
}
可能更新同步器的状态
如果线程位于队列中,则将其移除队列
返回成功 }
void release(){
更新同步器的状态
if( 新的状态允许某个被阻塞的线程获取成功 ){
解除队列中一个或多个线程的阻塞状态
}
}
图源:《并发编程的艺术》下图是独占式同步状态获取的流程
当某个线程争夺同步资源失败之后,他们都会将线程包装为节点,并加入CLH同步队列的队尾,并保持自旋,一个是addWaiter(Node.EXCLUSIVE),一个是addWaiter(Node.EXCLUSIVE)。
同步队列中的线程在自旋时会判断其前驱节点是否为首节点,如果是首节点node.predecessor() ==
head,他们都会尝试获取同步状态,只不过: 独占式获取状态成功后,只会出队一个节点。
共享式获取状态成功后,除了出队一个节点,还会唤醒后面的节点。
线程执行完逻辑之后,他们都会释放同步状态,释放之后将会unparkSuccessor(h)唤醒其后可被唤醒的某个后继节点。