AQS是JAVA中的一组抽象类,就是为了解决多线程并发竞争共享资源而引发的线程安全问题,细致点说AQS就是具备一套线程阻塞等待以及被唤醒的时候锁分配的机制,这个机制是由队列来实现的,暂时获取不到所的线程加入到队列里面,AQS本身并没有实现太多的业务功能,只是对外提供了三点核心内容来帮助实现其他的并发内容
1)由int类型修饰的state属性
比如说Reentranlock和ReentrankReadWriteLock获取到的所的方式都是针对于state变量做修改来实现的,state等于1代表有线程获取到锁,CountDown计数器和Semphore计数器赋初值就是根据state来赋值的,state代表计数器;
2)由Node对象组成的双向链表,比如说ReentranLock有一个线程没有拿到锁资源,需要将线程封装成Node对象,并加Node对象加入到双向链表中,然后将线程挂起,进行阻塞等待;
3)由Node对象组成的单向链表,叫做ConditionObject,比如说ReentranLock,一个线程持有锁,执行了await()方法,此时这个线程就被封装成Node对象,并且被添加到单向链表里面
1)当new了一个ReenTranLock的时候,AQS默认就是head=tail=null,state等于0
2)此时来了一个A线程,执行lock方法获取锁资源,CAS操作将state变成1,获取锁成功,A线程持有锁资源;
3)B尝试获取到锁资源,B线程尝试获取到锁资源,但是锁资源被A资源占用,先创建一个Node节点作为傀儡节点也就是头节点,然后将当前这个失败的线程封装成一个Node,加入到这个傀儡节点的后面
4)挂起B线程,当前有一个ws属性,如果ws是-1,表示后面节点被挂起,等到A线程释放锁资源将state变成0然后去唤醒B线程,唤醒head.next
5)B线程就可以尝试重新获取到锁资源
ReenTranLock没有直接继承AQS,当执行到lock方法的时候发现执行了sync的lock方法,sync是一个抽象类,继承了AQS,Sync有两个子类实现一个是公平锁,一个是非公平锁
FairSync NonFairSync
//非公平锁的lock实现 final void lock() { //线程到达以后直接尝试将state从0改成1,成功就拿到锁资源 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else//如果if中的CAS失败执行acquire方法直接走后续操作 acquire(1); } //公平锁的lock实现 final void lock() { acquire(1); }
一)acquire方法:
public final void acquire(int arg) { //1.查看tryAcquire方法,尝试再次去重新获取到锁,如果这个方法返回的是true,那么直接后面逻辑都不用走了 //2.查看addWaiter:没有获取到锁,要去排队了 //3.查看acquireQueued:挂起线程和后续被唤醒继续锁资源的逻辑 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
一)AQS的tryAcquire方法没有任何实现,必须被Reentranlock中的公平锁和非公平锁实现重写方法
非公平锁的实现:
1)没有线程持有锁,尝试获取锁
2)有线程获取锁,判断是不是可重入锁
3)如果上面两块都失败,直接返回false
final boolean nonfairTryAcquire(int acquires) { //1.获取到当前竞争锁失败的线程,获取到锁对象 final Thread current = Thread.currentThread(); int c = getState(); //2.然后再来进行判断公共状态state也就是c,然后再次尝试获取到锁 //state=0代表当前资源没有被锁住,此时当前线程可以尝试加锁占有资源 if(c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current);//将一个线程赋值给当前ReenTrankLock中的互斥锁拥有者的线程对象的属性 return true; } } //3.此时获取到锁还是失败,锁某一个线程持有判断当前获取锁失败的线程是都等于当前之前获取锁成功的线程,就是判断是否是可重入锁,当前线程是独占线程那么每一次就+1 //state值不等于0 判断当前线程和持有当前资源线程是不是同一个线程,是,那就是可重入锁逻辑,就累加 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires=1; if (nextc < 0) // 防止加满int溢出 throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
公平锁的实现:
1)hashQueedPredecessors()方法判断,判断自己是不是第一个
2)如果当前是队列的首元素,直接CAS尝试获取到锁compareAndSetState
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //虽然锁资源没有被持有,但是需要判断有没有别的线程正在排队; if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { //直接将拥有锁的线程设置为自己 setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } } protected final void setExclusiveOwnerThread(Thread thread) { exclusiveOwnerThread = thread; }
二)addWaiter方法:将当前线程封装成Node对象,并且插入到AQS的双向链表
三)acquireQueued方法
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); Node pred = tail;//当前队列尾节点是否存在 if (pred != null) {//当前队列没有进行初始化 node.prev = pred;//如果尾巴节点存在,直接插入到尾巴节点的后面 if (compareAndSetTail(pred, node)) { //CAS设置尾节点,CAS(tail,pred,node) pred.next = node; return node; } } enq(node);//创建队列 return node; }
//这个代码是为了制定假设有若干个线程同时执行入队操作况且此时队列仍然是null private Node enq(final Node node) { for (;;) {//死循环确保这个对应的节点一定可以入队 Node t = tail; if (t == null) { //如果尾巴节点等于null if (compareAndSetHead(new Node()) //需要注意的是,这里不是用的方法参数node,而是先创建了一个Node,并且head,tail都指向了这个空Node //并发环境下设置头节点CAS(head,null,new Node()) //队列只是需要一个线程创建就可以了,后续的线程直接插入到队列的结尾即可 tail = head;//设置完成头节点,当前节点也是尾巴节点=null } else { //执行入队操作,如果第一个线程创建队列成功,然后再走一次循环保证入队成功 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
AQS的工作流程:
1)线程请求同步资源:当一个线程请求某一个同步资源也就是尝试进行加锁的时候,AQS会尝试使用CAS来操作修改同步状态,如果成功获取到了锁,该线程可以继续执行
2)获取同步状态失败:如果说当前同步状态已经被其他线程占用了,锁被其他线程获取了,那么当前线程就需要将等待,AQS就会将该线程封装成一个节点,加入到双向链表中
3)自旋和阻塞:在等待队列中的线程会不断地进行自旋尝试获取到锁,如果自旋一定次数还是获取不到锁,那么就进入到阻塞状态,等待被唤醒
4)线程释放锁:当线程完成了对资源的操作需要释放锁的时候,这个线程就会调用AQS方法中的release方法,这个线程会使用CAS来修改同步状态,并唤醒等待队列中的一个线程或者是多个线程
5)等待唤醒线程:AQS在释放资源以后,会从队列中选择一个或者是多个线程并将其唤醒,被唤醒的线程会尝试再次去获取同步状态,如果获取成功,那么继续执行,如果获取失败,那么继续进入自旋或者是阻塞状态