14-并发类AQS

一、什么是AQS

AQS:是一个抽象类,java所有显示锁比如(ReentrantLock)都是通过个类实现的。

二、AQS类数据结构

AQS抽象类:采用了模版方法模式,这个类里面定义了一系列标准流程的模版方法,只要子类(比如ReentrantLock)继承这个模版类,并重写流程方法,那么就可以实现锁。
AQS中定义的模版方法:
1、acquire(int arg)//独占式-获取锁方法
2、acquireInterruptibly(int arg)//独占式-可响应中断获取锁
3、tryAcquireNanos(int arg, long nanosTimeout)//独占式-尝试超时机制获取锁
4、acquireShared(int arg) //共享式获取锁
5、acquireSharedInterruptibly //共享式-可响应中断获取锁
6、tryAcquireSharedNanos//共享式-尝试超时机制获取锁
7、realse//独占式释放锁
8、realseShare//共享式释放
AQS中定义的需要子类覆盖的流程方法:
1、tryAcquire 独占式获取
2、tryAcquireShared 共享式获取
3、tryRelease 独占式释放
4、tryReleaseShared 共享式释放
5、isHeldExclusively //当前锁是被占用
AQS底层数据结构
1、同步状态标志位volatile变量

  • private volatile int state;
    //同步状态标志位//volatile保证线程可见性,0:表示锁未被占用,1:表示锁被占用
  • getState()//获取状态同步器
  • setState()//设置状态同步器
  • compareAndSet// 用原子操作-设置同步器状态标志位

2、同步队列

同步队列:先进先出队列,每个元素是一个Node对象
Node对象主要结构:

1)、节点等待状态(volatile int waitStatus)

分别有5种值:

  • CANCELLED=1:超时需要移走
  • SIGNAL=-1:后继节点的线程处于等待的状态、当前节点的线程如果释放了同步状态或者被取消、会通知后继节点
  • CONDITION=-2:节点在等待队列中、节点线程等待在Condition、当其它线程对Condition调用了singal()方法该节点会从等待队列中移到同步队列中
  • PROPAGATE =-3:表示下一次共享式同步状态获取将会被无条件的被传播下去(读写锁中存在的状态,代表后续还有资源,可以多个线程同时拥有同步状态)
  • initial =0:表示当前没有线程获取锁(初始状态)

2)、prev前驱节点(指向前一个节点)、后继节点next(批向后一个节点)
3)、当前线等待线程

3、head节点、tail节点
那么可以得出 同步队列结构图如下:
在这里插入图片描述

三、ReentrantLock基于AQS的实现
1、非公平模式

1、先看一段调用方法

//定义一个锁
private Lock lock  = new ReentrantLock();
public void increament() {
    //获取锁
	lock.lock();
	try {
		count++;
	}finally {
	    释放获取的锁
		lock.unlock();
	}
}

2、new ReentrantLock()

//返回一个非公平锁
sync = new NonfairSync();

3、NonfairSync类
NonfairSync这个类继承了Sync,NonfairSync这个类只实现lock()、tryAcquire()这2个方法,并没有继承AbstractQueuedSynchronizer,没有实现其它模版类定义的流程实现类,而是由Sync类继承AbstractQueuedSynchronizer和实现了剩下的流程定义类,因为ReentrantLock实现了2种模式的锁:公平锁和非公平锁,这2种锁只有获取锁的方式不一样,其它一致,将其它方法抽象在了Sync类中,并由这个类继承AbstractQueuedSynchronizer类。
4、获取锁方法

public void lock() {
        //默认新建返回的是非公平锁,
        //所在这里调的是非公平锁的lock()实现方方法
        sync.lock();
 }
 //非公平锁中lock()
 final void lock() {
 //用compareAndSetState这个cas修改锁标志位(从0改成1),
 //如果修改成功,那么获取了锁,设置当前线程以独占形式拥有此同步器;
 //(这里就体现了非公平锁的,非公平性,获取锁方法里面开始就修改锁状态,
 //不管同步队列中是否有等待线程队列),而公平锁会判断
 if (compareAndSetState(0, 1))
       setExclusiveOwnerThread(Thread.currentThread());
   else
       //失败 ,调用AQS的acquire方法
       acquire(1);
}

AQS的acquire方法:

public final void acquire(int arg) {
        //tryAcquire 是流程方法,由子类ReentrantLock实现
        //尝试获取锁,如果获取失败,那么将这个节点加入到等待队列,
        //并调用acquireQueued进入自旋(即for死循环)
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

ReentantLock 实现的tryAcquire方法:

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
  }
final boolean nonfairTryAcquire(int acquires) {
     final Thread current = Thread.currentThread();
     //这里没有用同步方法获取state,因为state 是valilate类型
      int c = getState();
      if (c == 0) {
           //如果没有线程获取锁,那么获锁
          if (compareAndSetState(0, acquires)) {
              setExclusiveOwnerThread(current);
              return true;
          }
      }
      //如果当前线程是 占有锁的线程,那么直将状态+1,这里体现可重入性
      else if (current == getExclusiveOwnerThread()) {
         //可重入性这一段加1 操作为什么没有用原子操作,
         //因为当前线程就是持有锁的线程,
         //不会出现多个线程执行这段代码的情况,所以直接加1后 ,
         //设置state即可
          int nextc = c + acquires;
          if (nextc < 0) // overflow
              throw new Error("Maximum lock count exceeded");
          setState(nextc);
          return true;
      }
      return false;
  }

addWaiter(Node.EXCLUSIVE)方法:

private Node addWaiter(Node mode) {
        //将当前需要等待的线程打包成一个node节点
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;//获取尾节点
        if (pred != null) {//尾节点不为空
            node.prev = pred; //将尾点节赋值给当前节点prev
            if (compareAndSetTail(pred, node)) {//用cas操作将tail指向node
                pred.next = node;// 将node赋值给尾节点next
                return node;
            }
        }
        enq(node);//设置插入队列失败(说明有多个线程同时加入队列),调用enq
        return node;
    }

在这里插入图片描述
enq方法:
如果插入队列失败,那么会一直循环cas操作直到插入队列成功为止(这里就是利用cas操作实现锁的方法)

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
               // 尾节点为空,那么新建一个空节点,初始化head tail节点
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //tail不为空,cas操作设置当前节点为尾节点
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;//返回当前节点
                }
            }
        }
    }

//将等待线程插入到同步队列后,执行了acquireQueued方法,acquireQueued实现:

//参数:当前节点,1
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)) {
                //头节点释放锁,头节点后继节点获取锁成功后,
                //设置这个后继节点为头节点
                //(也就是说头节点是已经获锁的节点,只有头节点的后继节点会一尝试获取锁)
                setHead(node);  
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //如果不是头节点,或获取锁失败(头节点还未释放锁),
            //移除已经被取消的节点,然后阻塞,等待锁被释放后被唤醒
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
//setHead头节点方法
private void setHead(Node node) {
   head = node;
   node.thread = null;
   node.prev = null;
}

执行
在这里插入图片描述
移除被取消节点方法:

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //当前节点前驱节点状态
    int ws = pred.waitStatus;
    //后继节点处于等待状态,说明正常,返回
    if (ws == Node.SIGNAL)
        return true;
    //>0被取消,需要移出队列    
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev; //把前驱节点移除
        } while (pred.waitStatus > 0); //直到前驱前节点状态不为取消
        pred.next = node;
    } else {
        //设置前驱节点状态为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

执行获取锁后,队列状态
在这里插入图片描述

在这里插入图片描述

5、释放锁方法
lock.unlock();

public void unlock() {
    sync.release(1);//调用AQS的release方法
 }

AQS.release(1);:

public final boolean release(int arg) {
   if (tryRelease(arg)) {//tryRelease这个是AQS定义的流程方法,在ReentrantLock中实现
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //唤醒头节点的后继节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

unparkSuccessor,参数:头节点

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
         //设置头节点等待状态为0
        compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;//获取头节点后继节点
    if (s == null || s.waitStatus > 0) {//如果后继节点为空或已取消状态
        s = null; 
        //从尾节点向前找等待状态的节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
         //唤醒这个节点线程
        LockSupport.unpark(s.thread);
}
1、公平模式
final void lock() {
    //直接调用AQS方法
    acquire(1);
  }
public final void acquire(int arg) {
 //公平模式类中实现tryAcquire
  if (!tryAcquire(arg) &&
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         selfInterrupt();
 }
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;
}

非公平模式比公平模式效率高,因为非公平模式下 ,线程直接尝试获取,如果获取锁失败才阻塞,公平模式下:先要判断队列是否为空,不为空要加入同步队列,等锁被释放,线程需要被唤醒。而唤醒是比较耗时的。

四、ReentrantLock.condition条件等待/通知在AQS中的实现

1、调用

private Lock lock = new ReentrantLock();
private Condition c1= lock.newCondition();
 private Condition c2= lock.newCondition();
public void change(){
   lock.lock();
   try {
   	this.change= 1;
   	//改变条件,等待将c1条件上的等待的线程唤醒
   	c1.signalAll();
   }finally {
   	lock.unlock();
   }
}

public void wait(){
  	lock.lock();
  	try {
      	while(this.change==1) {
      		try {
      			c1.await();
  			} catch (InterruptedException e) {
  				// TODO Auto-generated catch block
  				e.printStackTrace();
  			}
      	}    		
  	}finally {
  		lock.unlock();
  	}
  }

lock和unlock方法不再介绍,就是获取锁就是 执行Lock后面的方法,没获取到锁 就加入同步队列,等待锁释放再尝试获取锁。
先看一下 lock.newCondition();对象的结构:

 public Condition newCondition() {
      //返回sync的ConditionObject对象
      return sync.newCondition();
  }

ConditionObject对象类
1、firstWaiter头等待节点 lastWaiter尾等待节点
2、signalAll,signal唤醒
3、await 等待
ConditionObject对象中维护了一个等待对队,结构如下:
在这里插入图片描述
那整 个ReentrantLock的结构如下:
在这里插入图片描述
等待线程获取锁后,判断条件是否满足,不满足调用await 方法:
将当前线程打包成node节点添加到等待队列中,释放锁,阻塞当前节点中的线程

public final void await() throws InterruptedException {
    if (Thread.interrupted())
         throw new InterruptedException();
         //将获取锁的头节点-》加入到等待队列中,
         //并将他的waitStatus设置成condition(表示在等待条件)
     Node node = addConditionWaiter();
     //释放锁 ,并将头点节的后继节点唤醒,这时后继节点从acquireQueued阻塞中被唤醒,
     //判断前驱节点是否是头节点(此时被唤醒的节点前驱节点还是指向头节点,
     //虽然这个头节点被加到了等待队列)
     //如果是,那么获取锁,获取成功那么将自己设置成了头节点;否则又阻塞等待下次被唤醒;
     //也就是说头节点离开同步队列是在被唤醒的线程中实现的,
     //获锁成功后它将自己设置成了头节点,
     //那么原来head的指向就跟原来头节点断开了)
     int savedState = fullyRelease(node);
     int interruptMode = 0;
     //如果节点不在同步队列中(说明被唤醒的后继节点线程已经拿到锁并将自己设置成了头节点),
     //将线程阻塞,, 等待满足条件的地方调用condition.signal
     while (!isOnSyncQueue(node)) {
         //阻塞当前线程
         LockSupport.park(this);
         //条件被唤醒,condition.signal被执行,检查被唤醒的原因,如果是因为中断,
         //那么跳出循环 (condition.signal方法会将当前线程从等待队列移到同步队列,
         //下面的signal方法分析会说明)
         if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
             break;
     }
     //通过signal被唤醒的线程,加入到同步队列后,
     //需要再次通过acquireQueued(自旋+cas)去竞争锁,
     //没获取到锁就是阻塞在这里,
     //其它获取锁线程释放锁后就是唤醒后继节点线程,
     //只有当这个线程获取锁后,就是从应用代码调用await()的地方返回,
     //然后可以执行后面逻辑
     if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
         interruptMode = REINTERRUPT;
     if (node.nextWaiter != null) // clean up if cancelled
         unlinkCancelledWaiters();
     if (interruptMode != 0)
         reportInterruptAfterWait(interruptMode);
 }

addConditionWaiter方法:

private Node addConditionWaiter() {
    //拿到同步队列尾节点
     Node t = lastWaiter;
     // 因为加入到等待队列的节点是从尾部插入的,
     //所以先判断当前尾节点是否已经取消,如果取消那么移出等待队列
     if (t != null && t.waitStatus != Node.CONDITION) {
         unlinkCancelledWaiters();
         t = lastWaiter;
     }
     //把当前线程打包成Node 加入到等待队列尾部
     Node node = new Node(Thread.currentThread(), Node.CONDITION);
     if (t == null)
         firstWaiter = node;
     else
         t.nextWaiter = node;
     lastWaiter = node;
     return node;
 }

fullyRelease:锁释放,并唤醒头节点的后继节点,

final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }
public final boolean release(int arg) {
   if (tryRelease(arg)) {
         Node h = head;
         if (h != null && h.waitStatus != 0)
             unparkSuccessor(h);
         return true;
     }
     return false;
 }

signalAll方法:

public final void signalAll() {
    if (!isHeldExclusively())
         throw new IllegalMonitorStateException();
     Node first = firstWaiter;
     if (first != null)
         doSignalAll(first);
 }
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first); //将头节点移到同步队列中
                first = next;
            } while (first != null);//从头节点开始将等待队列节点移到同步队列尾部
 }

transferForSignal

final boolean transferForSignal(Node node) {
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        //将这个节点加入到同步队列中    
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            //唤醒当前线程,这时AQS中await()方法 继续执行,
            //通过自旋+cas方式去竞争锁,直到获取锁后,
            //AQS中await()方法才返回
            LockSupport.unpark(node.thread);
        return true;
    }

调用condition代码流程总结:
先把这段代码再贴一遍

public void wait(){
  	lock.lock();
  	try {
      	while(this.change==1) {
      		try {
      			c1.await();
  			} catch (InterruptedException e) {
  				// TODO Auto-generated catch block
  				e.printStackTrace();
  			}
      	}    		
  	}finally {
  		lock.unlock();
  	}
  }

1、启动多个线程A,B,C,run方法中执行wait()
2、A、B、C线程执行wait()方法,调用lock.lock();竞争锁,A线程获取到锁继续向下执行;没有获取到锁的B、C线程被lock()方法加入到同步队列中(B、C线程在同步队列中,这2个线程会通过自旋+cas尝试获取锁,获取失败就被阻塞),直到获取A释放后将它的后继节点唤醒
3、A获取锁后,判断while条件是否满足,不满足调用await()方法,await方法将A加放到另一个等待队列,并释放锁->唤醒线程B-》将自己阻塞,等待condition.signal唤醒(此时await()方法无法返回就阻塞在这里),这时线程B在lock()方法中被唤醒,获取到锁,并将它自己设置成头节点。些时A才自式从同步队列中断开,然后线程B也会判断while里面条件 然后调用await()…… C线程同样重复以上
4、应用代码判断达到条件后,比如启用线程D,D线程会先lock.lock()竞争锁,竞争方式和上面一样
5、D线程获取锁后,执行condition.signalAll()方法,这个方法将等待队列中线程从头开始一个一个移到同步线程中,并唤醒节点中的线程
6、被唤醒的线程继续执行aqs中await()后面的代码,执行完后就返回了。此时,应用程序调用await()方法后面的代码就可以继续执行了。
以上是await() 到signalAll,线程在同步队列 等待队列中的切换
同步队列:用于竞争锁,等待获取锁的线程
等待队列:await()等待条件达到前一直阻塞,signal()条件达到后,将同步队列节点移到等待队列中去,然后唤醒自己,再通过自旋+cas 竞争锁,竞争失败阻塞,等锁被释放后 才被唤醒继续竞争锁。

总结

以上就是AQS抽象类,以及ReentrantLock基于AQS实现的锁方式,以及其它锁也是继承AQS实现的锁。主要是重写的流程方法,而其它都是AQS已经实现了。
AQS主要有2个对象
一个同步标志位:用于标记锁状态,并用原子方法修改状态
同步队列:一个先进先出的队列,用于存放等待锁的线程,头节点是已经获取锁的线程,当头节点释放锁后会把它的后继节点设置成头节点并唤醒它的后继节点
等待队列:用于条件不足从同步队列移到等待队列中 等待被signal的线程,条件达到后,从等待队列回到同步队列竞争锁。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值