一、AQS的模板模式解读
AbstractQueuedSynchronizer该类使用的设计模式是模板模式template pattern。
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
在AbstractQueuedSynchronizer类中,封装不可变部分的方法前都加了final,子类不能覆盖,比如:
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
而行为由父类控制,子类实现的方法为这5个:
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
AbstractQueuedSynchronizer类并没有实现这5个方法,而是直接抛出异常,需要它的子类去扩展。
AQS的子类这里以ReentrantLock中的Sync类为例
下图:蓝色实心箭头表示继承关系,红色线条表示内部类
Sync继承了AQS类,并实现了tryRelease和isHeldExclusively方法。
而AQS类的tryAcquire方法由Sync的两个子类,FairSync(公平锁)和NoFairSync(非公平锁)分别去实现 ,不同的 加锁方式。
AQS类的另外两个模板方法tryAcquireShared和tryReleaseShared,在ReentrantLock中不会实现,因为ReentrantLock是排它锁,不用实现共享锁的方法。
以调用ReentrantLock的公平锁的lock方法为例,用图表达:一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行这句话在代码中的提现:
AQS类的acquire方法定义了获取锁的整体逻辑,但是其中的tryAcquire逻辑由子类实现,不同的子类有不同的实现。
子类(FairSync)在获取锁的时候用的父类(AQS)的acquire方法,而父类(AQS)的acquire方法在执行获取锁的逻辑的时候运行的部分逻辑tryAcquire是调用的该子类重写了的逻辑。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
二、三大核心
1.AQS维护了一个volatile int state共享资源
/**
* The synchronization state.
*/
private volatile int state;
AQS使用一个int类型的成员变量state来表示同步状态,state=0表示同步状态可用(如果用于锁,则表示锁可用),state=1表示同步状态已被占用(锁被占用),在重入锁的情况下可以state>1。
AQS提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操作。
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
2.AQS维护了一个FIFO双向链线程等待队列
多线程争用资源被阻塞时会进入该队列。
源码中Node类保存着线程引用和线程状态的容器,它是同步队列中的每一个节点。
属性名称 | 描述 |
int waitStatus | 表示节点的状态。其中包含的状态有:
|
Node prev | 前驱节点 |
Node next | 后继节点 |
Node nextWaiter | 存储condition队列中的后继节点 |
Thread thread | 入队线程 |
debug调试AQS入队出队示例
在main中起3个线程。Thread-3最先执行,Thread-1,Thread-3一次排队。
代码:
package com.sid;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @program: thread-test
* @description:
* @author: Sid
* @date: 2019-01-14 14:11
* @since: 1.0
**/
public class AQSTest {
public static int threadTotal = 3;
public static int count = 0;
private final static Lock lock = new ReentrantLock(true);
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < threadTotal ; i++) {
executorService.execute(new Runnable() {
public void run() {
try {
doSomeThing();
} catch (Exception e) {
System.out.println(e);
}
}
});
}
executorService.shutdown();
}
private static void doSomeThing() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
调试步骤及解说:
1.Thread-3先获取锁lock.lock(),一直不要释放,不要运行finally中的lock.unlock()。
此时AQS中
int state=1
Thread exclusiveOwnerThread=Thread-3
2.Thread-1去获取锁,获取不到,进入AQS的Sync Queue同步队列,并且park挂起。
第一次进入同步队列的时候会触发创建一个不封装线程的head头节点
private Node addWaiter(Node mode) {
...
//该方法触发创建一个不封装线程的head节点
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;
}
}
}
}
3.Thread-2去获取锁,获取不到,进入AQS的Sync Queue同步队列,排在Thread-1的node后面,并且park挂起。
4.等 Thread-1,Thread-2都进入同步队列等待之后,放开Thread-3的端点,让Thread-3走到finally中的lock.unlock()方法中。
Thread-3释放资源(tryRelease(arg)方法)的后,AQS类的int state=0 并且 Thread exclusiveOwnerThread=null。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
如果当天Sync Queue同步队列中,head头节点不为空,并且头节点的等待状态waitStatus!=0,
那么把头节点head的等待状态waitStatus设置为0,并且unpark唤醒head.next节点中的线程。这里即是唤醒了Thread-1线程。
Thread-1线程被唤醒后,继续执行AQS中acquireQueued方法。
如果它的prev前驱节点是head头节点,那么执行tryAcquire(arg)获取锁。
获取锁成功后会把自己变成head头节点,把自己封装的线程设置为空,并且把原来的头节点移除队列。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
//获取锁成功后会把自己变成head头节点
setHead(node);
//把原来的头节点移除队列
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
//睡去的入口,被唤醒的出口
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
此时的Sync Queue变成了:
5.Thread-1线程执行完成后,调用lock.unlock()方法释放资源,唤醒Sync Queue的下一个线程Thread-2。
与上面唤醒Thread-1线程的逻辑雷同,这里就只贴出唤醒Thread-2时,同步队列Sync Queue的变化
由
变成了
3.AQS中维护了ConditionObject队列
一个Lock可以创建多个Condition。
Condition对象只能用于独占模式。
ConditionObject使用相同的内部队列节点,但是维护在一个单独的单项条件队列中,也是链表,当Sync Queue队列中的node执行到await的时候,节点会转移到Condition Queue中。当收到signal操作的时候将Condition Queue条件队列的节点转移到Sync Queue等待队列中。
在main中期4个线程,thread-1,thread-2,thread-3,thread-4。
代码:
package com.sid;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @program: thread-test
* @description:
* @author: Sid
* @date: 2019-01-14 17:13
* @since: 1.0
**/
public class ConditionTest {
private final static Lock lock = new ReentrantLock(true);
public static void main(String[] args) {
Condition condition1 = lock.newCondition();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
System.out.println("thread-1 "+" do ");
} finally {
lock.unlock();
}
}
});
t1.setName("Thread-1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
waitCondition1( condition1,"Thread-2","condition1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.setName("Thread-2");
t2.start();
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
waitCondition1( condition1,"Thread-3","condition1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t3.setName("Thread-3");
t3.start();
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("Thread-4 get lock");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
condition1.signal();
System.out.println("Thread-4 send condition1 signal ~ ");
lock.unlock();
}
});
t4.setName("Thread-4");
t4.start();
}
private static void waitCondition1(Condition condition,String threadName,String conditionName) throws InterruptedException {
lock.lock();
try {
System.out.println(threadName+" wait "+conditionName+" signal");
condition.await();
System.out.println(threadName+" get "+conditionName+" signal");
} finally {
lock.unlock();
}
}
}
步骤:
1.thread-1 lock.lock()获取锁后,不要释放锁,不要执行unlock。
2.thread-2 开始执行,获取不到锁,进入Sync Queue队列中等待。
3.thread-3 开始执行,获取不到锁,进入Sync Queue队列中等待。
4.thread-4 开始执行,获取不到锁,进入Sync Queue队列中等待。
5. thread-1执行lock.unlock()释放锁,thread-2将被唤醒去竞争锁。
线程2在Sync Queue中被unpark唤醒,获取到锁。
将head指向自己,将自己node封装的thread=null。
把以前的head node出列。
执行后面的业务逻辑到condition.await()
进入Condition Queue队列,
释放锁,将waitStatus=0,
unpark唤醒Sync Queue中Next Node,即是Node@825 thread-3
park睡眠自己。
6.thread-3被唤醒,将去竞争锁
线程3在Sync Queue中被unpark唤醒,获取到锁。
将head指向自己,将自己node封装的thread=null.
把以前的head node 即是Node@641出列。
执行后面的业务逻辑到condition.await()
进入Condition Queue队列
释放锁,将waitStatus=0
unpark唤醒Sync Queue中Next Node,即是Node@928 thread-4
park睡眠自己
7.thread-4将被唤醒,去竞争锁
线程4在Sync Queue中被unpark唤醒,获取到锁。
将head指向自己,将自己node封装的thread=null。
把以前的head node 即是Node@825出列。
执行后面的业务逻辑到condition.signal()。
将Condition Queue中的fistWaiter即是Node@1104 thread-2出列
把fistWaiter指向1104的nextWaiter 即是Node@1243 thread-3.
将1104的nextWaiter=null,waitStatus=0.
将1104的进入Sync Queue队列的末尾。
Node@928的waitStatus=-1.
Sync Queue中的Node@928 thread-4执行完逻辑后释放锁
将Node@928的waitStatus=0
unpark唤醒Sync Queue中 928的next Node 即是Node@1104 thread-2
三、两种资源共享方式
1.Exclusive
独占锁。只要AQS的state变量不为0,并且持有锁的线程不是当前线程,那么代表资源不可访问。
比如ReetrantLock就是用的这种方式。
2.Share
共享锁。只要AQS的state变量不为0,那么代表资源不可以为访问。
比如CountDownLatch就是用的这种方式。它使一个或多个线程等待一组事件发生。
闭锁状态包括一个计数器,该计数器被初始化为一个正数,表示需要等待的事件数量。
执行countDown方法递减计数器,表示有一个事件已经发生,
await方法等待计数器达到零,表示所有需要等待的时间都已经发生。
如果计数器值非零,那么await会一直阻塞直到计数器为零,或者等待线程中断,或者等待超时。