AQS同步器框架 源码

state 同步器中共享资源,它是volatile类型确保内存可见性。


Node 线程等待队列,保存线程。节点等待状态 CANCELLED 取消等待,需要在节点中移除。SIGNAL 等待唤醒。0 初始状态。CONDITION 节点处于等待CONDITION。PROPAGATE 共享模式,节点线程处于可运行状态。


head 首节点。


tail 尾节点。


tryAcquire 尝试获取独占资源,成功返回true,失败返回false,这是一个空方法,由自定义同步器去实现获取资源的方式。


addWaiter 使用当前线程和模式创建新节点,把新节点保存到队尾,成功后返回新节点。执行过程:先使用当前线程和模式创建新的等待队列节点node,如果当前对尾节点tail不空,把node的前置节点引用指向队尾节点,然后cas更新队尾节tail点为新建的node.如果成功则把之前的tail的next引用指向新建的node,然后返回node。如果没有返回node,则调用入队列enq。cas和自旋来更新tail节点为新建的node.


enq 入队列。在循环体中,如果队尾节点tail是空,则创建一个空节点,cas操作队尾tail指向这个空节点, 对首head指向这个空节点。再次循环,tail不为空,则把参数节点的前置节点指向这个空节点,cas更新队尾节tail点为参数node,空节点的next指向参数节点。


acquireQueued 在队列中进行资源获取。自旋 获取当前节点的前置节点,如果前置节点是首节点head,则当前节点则为第二个节点,尝试获取资源,获取成功将当前节点设置为首节点,把原首节点的next设置为null以便GC.返回false。如果没有返回,则不是首节点或者是首节点但获取资源失败,则调用shouldParkAfterFailedAcquire检查是否需要挂起,如果返回true则挂起当前线程。整个过程被中断过返回true,否则返回false。


shouldParkAfterFailedAcquire 检查是否需要挂起线程:如果前置节点的等待状态是SIGNAL等待唤醒返回true,如果前置节点状态是取消,则移除前置节点,检查新的前置节点状态,直到前置节点不是取消为止。如果前置节点不是唤醒SIGNAL也不是取消状态,则cas操作修改前置节点的状态为SIGNAL等待唤醒,返回false。


cancelAcquire:取消获取:获取当前节点的前置节点,如果状态是取消则移除前置节点,直到前置节点状态不是取消为止。把当前节点状态设置为取消。如果当前节点是尾节点,把前置节点的设置为尾节点,并把前置节点的下一个节点设置为null。否则:如果前置节点不是首节点,且状态是等待唤醒 则移除当前节点,否则(当前节点是首节点或者状态是propagete)则唤醒最靠前的那个没有被取消的节点。


acquire 获取资源:1首先尝试获取资源,获取到返回true,获取不到返回false,在AQS同步器框架这个方法是一个空的方法,需要自定义同步器根据自己需求去实现获取资源的方法。如果获取到了资源整个方法结束,执行完毕。如果获取不到资源,则使用当前线程和获取资源的模式创建队列的节点,保存到等待队列的尾节点上,然后在队列中获取资源。在队列中获取资源:自旋 首先判断如果当前节点的前置节点是队列首节点,也就是当前节点是第二个节点则尝试获取资源,获取成功则移除首节点,把当前节点标记为首节点,返回记录的中断状态,如果没有返回,则检查是否需要挂起线程,如果前置节点的状态是signal唤醒状态,则挂起;如果前置节点状态是cancel撤销状态,则移除前置节点,直到前置节点不是撤销状态为止,本次不挂起;如果前置节点不是等待唤醒和撤销,则更新为等待唤醒状态,本次不挂起;最后没有挂起,再重新执行自旋里的逻辑,直到挂起或者获取资源成功。挂起后,如果线程被中断,则撤销中断,并记录中断状态,以便获取资源成功后返回中断状态,如果中断过,则补上中断操作。


tryRelease 尝试释放资源,释放成功state==0返回true,否则返回false。这是一个空方法,由自定义同步器去实现。


unparkSuccessor 唤醒后继节点,如果后继节点不是空且没有被取消则,唤醒这个节点。否则从队列尾部往前查找,直到查找到队列最前面的那个状态不是取消的节点,把这个节点唤醒。


release 释放独占资源,先尝试释放资源,释放成功state==0,然后唤醒等待队列中从第二个最靠前的那个节点,让这个节点的线程变为可调度。




tryAcquireShared 尝试获取共享资源,返回负数获取失败,返回0获取成功,没有剩余可用资源,返回正数成功,有剩余可用资源,这是个空方法,需要自定义同步器实现。


setHeadAndPropagate 把当前节点设置为首节点,资源数充足唤醒后继共享节点。


doReleaseShared唤醒后继共享节点 自旋 首个节点的状态是SIGNAL等待唤醒,cas修改首个节点状态为0,失败重试,成功后唤醒后继最近的且不是撤销的一个节点。如果首个节点的状态是0,cas修改首个节点状态为PROPAGATE可运行状态,失败重试。首节点没变更退出,若变更,唤醒的线程修改了首节点,自旋。


doAcquireShared 使用当前线程共享模式创建节点,作为线程等待队列的尾节点。自旋 如果当前节点是第二个节点也就是它的前置节点是首个节点,尝试获取共享资源,如果资源数大于等于0,则将当前节点设置为首节点且唤醒一个离队首最近的后继共享节点。如果线程挂起后被中断过则补上中断,然后返回。如果不满足获取资源的条件或者获取资源失败,则坚持是否挂起,如果前置节点是SIGNAL,则执行挂起,如果是撤销则移除前置节点,使用前置的前置做为前置,如果还是撤销,则还是移除直到不为撤销为止,如果前置不是SIGNAL不是撤销则设置前置的状态为SIGNAL,自旋。此外挂起后,如果中断,会先撤销中断,记录中断标记,直到获取资源成功后,会补上中断。


acquireShared 共享模式获取资源:首先尝试获取资源,成功直接返回,失败则使用当前线程共享模式创建节点保存到线程等待队列队尾,在队列中获取资源。自旋 如果当前节点是第二个节点,也就是它的前置是首个节点,则尝试获取资源,如果成功把当前节点设置为首个节点,如果剩余资源大于0尝试唤醒1个后继共享节点,唤醒过程:自旋 如果当前节点的状态是signal唤醒状态,cas设置状态为0,失败重试,成功唤醒后继最近的一个不是取消的节点,如果当前节点状态是0,则cas修改节点状态为propagate可运行,失败重试。如果首节点已经变更,说明唤醒的线程获取了资源设置了首节点,则自旋,没变更则完成唤醒。如果线程在挂起时候被中断了,则在获取到资源前撤销中断,记录中断标记,获取到资源后补上中断。如果不是第二个节点或获取资源失败,则坚持挂起,如果前置节点是signal则挂起,如果前置节点是撤销,移除这个前置,如果前置还是撤销则移除,直到前置不是撤销为止,如果前置不是signal不是撤销则修改为signal等待唤醒,自旋,直到获取到资源为止。


tryReleaseShared 尝试释放指定数量资源,成功返回true,失败返回false。空方法,自定义同步器实现。


releaseShared 释放资源 首先尝试释放指定数量资源,失败返回false,成功尝试则唤醒后继共享节点,自旋 如果首个节点状态是signal等待唤醒,cas修改为0失败重试,成功唤醒最近的一个不为取消的后置节点,如果首个节点状态是0 则修改状态为propagete可运行状态,如果首节点没有被重新设置则结束否则自旋,最后返回true结束方法。


addConditionWaiter 把当前线程所在节点加入到条件队列。首先移除条件队列中状态不是CONDITION状态的节点,使用当前线程创建CONDITION状态节点,添加到条件队列的队尾。


fullyRelease 释放资源,执行过程 独占模式释放资源,释放成功返回资源数结束方法;释放失败 更新节点状态为取消,抛出异常。


await 等待:如果线程被中断则抛出异常结束。移除条件队列中状态不是CONDITION状态的节点,使用当前线程创建CONDITION状态节点,添加条件队列的对尾。加入条件队列之后,进行释放资源,成功返回释放的资源数,失败则修改当前节点的状态为取消,然后抛出异常。如果当前节点没有被添加到线程等待队列中则自旋:挂起当前节点线程,直到由于中断或者其他线程调用signal唤醒当前挂起的线程,中断会先撤销中断状态,最后补上,中断或者signal方法它们都会去执行cas操作把节点状态从CONDITION变为0,当然只有一个线程执行成功,然后把当前节点添加到线程等待队列中,结束自旋。执行独占模式在队列中获取资源。在条件队列中,如果当前节点不是最后一个节点,则移除条件队列中状态不是CONDITION的节点。最后,如果是由于当中断唤醒挂起的线程,如果是中断状态下把节点添加到线程等待队列的,则抛出异常,如果是其他线程调用signal把当前节点添加到线程等待队列的则补上中断。




transferForSignal 当前节点加入到线程等待队列:cas修改当前节点的状态由CONDITION变为0,失败后返回false.把当前节点加入到线程等待队列对尾,如果当前节点的前置节点状态是取消或者cas修改前置节点的状态为signal失败,则直接唤醒当前节点的线程。


doSignal 在条件队列的队首开始移除节点,对移除的节点保存到线程等待队列,保存成功结束方法,失败操作下一个节点直到保存成功。


signal 在条件队列上唤醒:对条件队列,从队首移除节点并把移除的节点尝试保存到线程等待队列,直到保存成功返回,失败操作下一个节点,保存过程:首先cas操作修改当前节点的状态从CONDITION改为0,如果失败跳过该节点,成功则把该节点添加到线程等待队列的队尾,返回其前置节点,如果前置节点状态是取消或者cas修改前置节点的状态为signal失败,则唤醒该节点线程。


条件队列的一点说明:线程1调用await进入挂起后(假如始终没有中断),线程2调用signal后,并没有立即唤醒线程1,而是线程2调用释放独占资源(如unlock)方法,才会去唤醒线程等待队列中首个状态不是取消的节点,使得线程1有机会被唤醒。

条件队列的一个例子:

package cn.cyc.n1;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

public class CD {

	public static void main(String[] args) throws InterruptedException {
		final Lock lock=new ReentrantLock();
		final Condition condition = lock.newCondition();
		
		Thread t1 = new Thread(new Runnable() {
			public void run() {
				lock.lock();
				try{
					System.out.println(Thread.currentThread().getName()+"获取独占锁");
					try {
						condition.await();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"当我获取到了条件,还需要等待别的线程释放了锁才可以运行");
				}finally{
					System.out.println(Thread.currentThread().getName()+"释放了独占锁");
					lock.unlock();
				}
				
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			public void run() {
				lock.lock();
				try{
					System.out.println(Thread.currentThread().getName()+"获取独占锁");
					LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(3));
					condition.signal();
					System.out.println(Thread.currentThread().getName()+"唤醒了其他节点");
				}finally{
					System.out.println(Thread.currentThread().getName()+"如果不释放独占锁,另一个线程虽然被唤醒但是会在线程等待队列等待");
					LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(3));
					System.out.println(Thread.currentThread().getName()+"释放了独占锁");
					lock.unlock();
				}
			}
		});
		
		t1.start();
		t2.start();
		t1.join();
		t2.join();

	}

}

输出:

Thread-0获取独占锁
Thread-1获取独占锁
Thread-1唤醒了其他节点
Thread-1如果不释放独占锁,另一个线程虽然被唤醒但是会在线程等待队列等待
Thread-1释放了独占锁
Thread-0当我获取到了条件,还需要等待别的线程释放了锁才可以运行

可以使用1个Lock创建多个条件队列,从而可以通知一组特定的线程。而内置锁条件队列,只能通知一个或全部,aqs条件相对于它更灵活

package cn.link.n1;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

public class CL {
	private final Lock lock=new ReentrantLock();
	private final Condition g1=lock.newCondition();
	private final Condition g2=lock.newCondition();
	
	
	
	public void mg1(){
		lock.lock();
		System.out.println(Thread.currentThread().getName()+" in mg1");
		try{
			try {
				g1.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+" out mg1");
		}finally{
			lock.unlock();
		}
	}
	
	public void mg2(){
		lock.lock();
		System.out.println(Thread.currentThread().getName()+" in mg2");
		try{
			try {
				g2.await();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+" out mg2");
		}finally{
			lock.unlock();
		}
	}
	
	public void sg1(){
		lock.lock();
		try{
			g1.signalAll();
		}finally{
			
			lock.unlock();
			System.out.println("sg1 release");
		}
	}
	
	public void sg2(){
		lock.lock();
		try{
			g2.signalAll();
		}finally{
			lock.unlock();
			System.out.println("sg2 release");
		}
	}
	
	public static void main(String[] args) {
		final CL c=new CL();
		new Thread(new Runnable() {
			public void run() {
				c.mg1();
			}
		}).start();
		
		new Thread(new Runnable() {
			public void run() {
				c.mg1();
			}
		}).start();
		
		new Thread(new Runnable() {
			public void run() {
				c.mg2();
			}
		}).start();
		LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
		c.sg1();
		LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
		c.sg2();
	}
}

Thread-0 in mg1
Thread-1 in mg1
Thread-2 in mg2
sg1 release
Thread-0 out mg1
Thread-1 out mg1
sg2 release
Thread-2 out mg2

sg1唤醒了线程0和1,sg2唤醒了线程2,他们使用同一个Lock锁。

3 AQS原理
AQS同步器框架定义了多线程访问共享资源的同步器,使用它可以创建自定义同步器,如ReentrantLock CountdownLatch Semophore等都是在AQS基础上实现的。AQS访问共享资源的方式有两种独占模式ReentrantLock和共享模式CountdownLatch和Semophore等。独占模式 获取资源:首先使用自定义同步器的尝试获取资源的方法尝试获取资源,如果获取失败,使用当前线程创建独占模式节点添加到线程等待队列的队尾,等待在队列中获取资源,在队列中获取资源:自旋 如果当前节点是第二个节点,尝试获取资源,获取成功,设置当前节点为首个节点,如果中断过,补上中断,如果不是第二个节点,或者获取资源失败,检查是否挂起,如果前置节点的状态是SIGNAL则挂起,如果中断唤醒,则撤销中断记录中断标记等待获取到资源后补上中断,如果前置节点的状态是取消则不断移除,直到前置节点状态不是取消为止,如果前置节点状态不是取消也不是SIGNAL则修改状态为SIGNAL 继续自旋,最终获取到资源退出,如果抛出异常,执行异常退出操作,如果当前节点的前置节点是取消,则移除直到前置节点不是取消为止,标记当前节点状态为取消,如果当前节点是尾节点,则移除当前节点,如果不是如果当前节点前置节点不是首节点且状态是SIGNAL则移除当前节点,否则唤醒当前节点之后首个状态不是取消的节点。独占模式 释放资源:首先使用自定义同步器的方法尝试释放资源,释放成功state=0,释放成功后唤醒线程等待队列中首个状态不是取消的节点线程。共享模式 获取资源:首先使用自定义同步器获取共享资源的方法获取资源,返回负数失败,0成功但没有剩余资源,正数成功有剩余资源,失败后使用当前线程创建共享模式节点,添加到线程等待队列的队尾,在队列中获取资源,自旋:如果当前节点是第二个节点,尝试获取资源,成功把当前节点设置为首节点,如果有剩余资源,则执行唤醒操作,唤醒过程:自旋 如果当前节点状态是SIGNAL,cas修改状态由signal变为0,失败重试,成功唤醒线程等待队列中首个状态不是取消掉 节点,否则如果状态是0 则cas操作修改状态由0改为PROPAGET,如果首节点没变化退出自旋,如果被中断过则补上中断。如果当前节点不是第二个节点,或者获取资源失败,则坚持是否挂起节点,如果当前节点的前置节点是SIGNAL则挂起,如果中断唤醒则先撤销中断,记录中断标记,等到获取资源后补上中断,如果前置节点的状态是取消,则移除前置节点,直到状态不是取消为止,如果不是SIGNAL也不是取消,则CAS修改设置状态为SIGNAL,继续自旋,直到获取到资源退出,如果抛出异常,执行异常退出操作,如果当前节点的前置节点是取消,则移除直到前置节点不是取消为止,标记当前节点状态为取消,如果当前节点是尾节点,则移除当前节点,如果不是如果当前节点前置节点不是首节点且状态是SIGNAL则移除当前节点,否则唤醒当前节点之后首个状态不是取消的节点。共享模式 释放资源:首先调用自定义同步器释放共享资源的方法,释放成功后,执行唤醒操作:自旋 如果首节点的状态是SIGNAL,则CAS修改状态由SIGNAL改为0,失败重试,成功则唤醒线程等待队列中首个状态不是取消的节点。条件队列 等待:如果线程中断,抛出异常;如果条件队列最后一个节点的状态不是CONDITION,移除条件队列中不是CONDITION的节点,使用当前线程创建CONDITION节点,添加条件队列的队尾;释放资源 使用独占模式释放资源,释放失败,修改当前节点状态为取消,抛出异常;检查挂起 自旋 如果当前节点不再线程等待队列中,则挂起当前节点线程,中断或者其他线程调用signal都会唤醒,如果中断唤醒会先撤销中断,最后补上中断,中断或者其他线程调用signal都会cas修改当前节点的状态由CONDITION改为0,然后把节点添加到线程等待队列中,最终只有一个线程执行成功;独占模式在队列中获取资源;获取资源后,如果当前节点不是最后一个节点,则移除条件队列中状态不是CONDITION的节点;如果中断唤醒 如果中断把节点添加到条件队列里,则抛出异常,否则是signal添加的,则补上中断。 条件队列 唤醒:如果没有独占资源则,抛出异常,从队首开始移除节点,cas修改节点状态由condition改为0,成功,则把当前节点添加到线程等待队列中,如果线程等待队列中前置节点状态是取消或cas修改为signal失败,则唤醒当前节点线程。 

猜你喜欢

转载自blog.csdn.net/liangwenmail/article/details/80991598