AQS(Abstract Queued Synchronizer)
抽象的队列的同步器,AQS定义了一套多线程访问共享资源的同步器,很多同步类底层都是它,如ReentrantLock、Semaphore、CountDownLatch…。
底层实现
拿ReentrantLock的lock()举例:
以上为它的底层调用图,我们可以看到,最底层AQS的acquire()方法调用的是子类重写的tryAcquire()方法,而未用自己的tryAcquire()方法,自己的tryAcquire()方法是为了给子类重写的,这其实是一种模板方法。
最后我们可以看到,到nofairTryAcquire时才是真正的调用了,它用了一个getStare(),我们再往下看看。
可以看到,最终是一个volatile的int类型的state,而这则是模型的关键。
AQS模型
上面说过,AQS是很多个锁的底层实现,而state是什么,则看是什么锁实现了它。
若为ReentrantLock,则state初始为0,加锁则state加1,解锁则state减1,相当于一个计数器。
若为CountDownLatch,则state则为初始设的门栓值,若为5,则state为5,每一次latch.countDown();,state都会减1,到0时解开。
模型底下的node队列,其实时一个双向链表,每一个node中存放着线程,以及前一个及后一个的地址。
线程获得锁,或者等待,都要进入该队列中。我们再看看刚刚的这个源码:
如果c==0,会用CAS的方式去抢这一把锁,若抢到了,则当前线程独占这把锁(ReentrantLock的)。
else if那里,倘若当前线程就是正在独占锁的线程,c+acquires,便是可重入了。
面试题:为什么说AQS的底层是由volatile和CAS实现的呢?
答:AQS底层的模型则是一个int类型的state和一个存放线程的双向链表。state是由volatile修饰的,让其具备可见性,而它的作用或者说对它的修改,是由锁的类型决定的,如ReentrantLock,则是初始化0,lock()即加1,unlock()即减1;又好比说CountDownLatch,初始化锁时设的门栓值就是state的初始值,每一次latch.countDown();,state都会减1,到0时解开。而CAS则是线程尝试改state值的方式,拿ReentrantLock的unfair(非公平)举例,我看了源码,是采用了comepareAndSetState()的方法,期望值是0,尝试把它改成1,改成了则拿到了锁,并把当前线程设为独占的线程。
**注意:**无论公平与非公平,队列中的线程都是先进先出的。这里的非公平,是指有一个线程来了,先用CAS尝试去获取锁,若不成功,就进入队列中;而公平呢,则是多了一个hasQueuedPredecessors() 方法判断是否有其它线程等待获取锁的时间超过当前线程,如果有返回true,则表明同步队列中已有线程先行进行等待获取锁,然后进入等待队列。
CLH队列
一共同步队列,AQS中的同步队列不是CLH队列,是CLH队列的一个变种。
AQS中,线程在队列中是被阻塞了(为了不耗cpu资源),而正统的CLH队列,是在队列中进行自旋,CLH队列原是C#语言下的一种数据结构,后被java借鉴。
写一个简单的公平锁(未写可重入、打断等处理)
public class MyLock {
private volatile int state=0;
//拿到锁的线程
private Thread lockHolder;
//创建一个队列
private ConcurrentLinkedQueue<Thread> queue=new ConcurrentLinkedQueue<>();
//state与lockHolder的set与get方法
public int getState() {return state; }
public void setState(int state) { this.state = state; }
public Thread getLockHolder() { return lockHolder; }
public void setLockHolder(Thread lockHolder) { this.lockHolder = lockHolder; }
//获得锁的方法
private boolean tryAquire(){
Thread t=Thread.currentThread();
int state=getState();
//判断锁是否被占有
if (state==0){
//先判断队列中是否为空,若不为空,再判断是否为队列的头节点(公平),
//若满足,则尝试去改state的值,都为true,则设当前线程为拿到了锁的线程
if ((queue.size()==0||t==queue.peek())&&compareAndSwapState(0,1)){
setLockHolder(t);
return true;
}
}
return false;
}
//lock方法
public void lock(){
//尝试改值,改值成功则拿到锁
if (tryAquire()){
return;
}
//若不成功,则将当前线程加入队列中
Thread current=Thread.currentThread();
queue.add(current);
for (;;){
//等待队列中写一个死循环,若为头节点,则再尝试改值
if (current==queue.peek()&&tryAquire()){
System.out.println("hold lock Thread-name:"+current.getName());
//改值成功,将其扔出队列
queue.poll();
return;
}
//不为头节点或未成功改值,阻塞
LockSupport.park(current);
}
}
//unlock方法
public void unlock(){
Thread current=Thread.currentThread();
//判断如果不是拿到锁的线程,抛异常
if (current!=lockHolder){
throw new RuntimeException("不是持有锁的线程无法释放!");
}
int state=getState();
//尝试将state改为0(释放锁)
if (compareAndSwapState(state,0)){
System.out.println(String.format("Thread-name:%s,释放锁成功!",current.getName()));
//改成功,将持有锁线程设为null
setLockHolder(null);
Thread head=queue.peek();
//如果队列的头不为空,则唤醒头线程
if (head!=null){
LockSupport.unpark(head);
}
}
}
//尝试改值的方法(参考AQS源码)
public final boolean compareAndSwapState(int oldValue,int newValue){
//调用unsafe类的CASint方法(AtomicInteger底层用的就是该方法)
return unsafe.compareAndSwapInt(this,stateOffset,oldValue,newValue);
}
//实例一个unsafe,下面就是通过unsafe反射取得上面的state值
private static final Unsafe unsafe=Unsafe.getUnsafe();
private static final long stateOffset;
static {
try {
stateOffset=unsafe.objectFieldOffset(MyLock.class.getDeclaredField("state"));
} catch (Exception e) {
throw new Error(e);
}
}
}
下一篇:学习笔记【多线程-第十三节:ConcurrentHashMap】
上一篇:学习笔记【多线程-第十一节:JUC Tools】