抽象同步队列AQS详解

本文深入探讨了AQS(AbstractQueuedSynchronizer)的原理,它是Java并发包中实现锁和其他同步组件的基础。AQS维护了一个FIFO双向队列,通过state变量进行线程同步。锁的实现如ReentrantLock和Semaphore利用AQS的独占和共享方式管理资源。文章还介绍了条件变量、中断处理以及自定义锁的实现,展示了AQS在生产者消费者模型中的应用。
摘要由CSDN通过智能技术生成

AbstractQueuedSynchronizer抽象同步队列简称AQS,它是实现同步器的基础组件,并发包中锁的底层就是使用AQS实现的。

类图

1、AQS是一个FIFO的双向队列,其内部通过节点head和tail记录队首和队尾元素

2、队列元素的类型为Node。其中Node中的thread变量用来存放进入AQS队列里面的线程

3、Node节点内部的SHARED用来标记该线程是获取共享资源时被阻塞挂起后放入AQS队列的

4、EXCLUSIVE用来标记线程是获取独占资源时被挂起后放入AQS队列的

5、waitStatus记录当前线程等待状态,可以为CANCELLED(线程被取消了)、SIGNAL(线程需要被唤醒)、CONDITION(线程在条件队列里面等待)、PROPAGATE(释放共享资源时需要通知其他节点)

6、prev记录当前节点的前驱节点,next记录当前节点的后继节点。

state

在AQS中维持了一个单一的状态信息state。可以通过getState、setState、compareAndSetState函数修改其值。

锁类型

state含义

ReentrantLock

当前线程获取锁的可重入次数

ReentrantReadWriteLock

state的高16位表示读状态,也就是获取该读锁的次数,低16位表示获取到写锁的线程的可重入次数

semaphore

当前可用信号的个数

CountDownlatch

state用来表示计数器当前的值

对于AQS来说,线程同步的关键是对状态值state进行操作。根据state是否属于一个线程,操作state的方式分为独占方式和共享方式。

独占方式

//获取资源

void acquire(int arg)

void acquireInterruptibly(int arg)

//释放资源

boolean release(int arg)

使用独占方式获取的资源是与具体线程绑定的,就是说如果一个线程获取到了资源,就会标记是这个线程获取到了,其他线程再尝试操作state获取资源时会发现当前该资源不是自己持有的,就会在获取失败后被阻塞。例子独占锁ReentrantLock。

共享方式

//获取资源

void acquireShared(int arg)

void acquireSharedInterruptibly(int arg)

// 释放资源

boolean releaseShared(int arg)

共享方式的资源与具体线程是不相关的,当多个线程去请求资源时通过CAS方式竞争获取资源,当一个线程获取到了资源后,另外一个线程再次去获取时如果当前资源还能满足它的需要,则当前线程只需要使用CAS方式进行获取即可。例子Semaphore信号量

独占锁ReentrantLock实现原理

当一个线程获取了ReentrantLock的锁后,在AQS内部会首先使用CAS操作把state状态值从0变为1,然后设置当前锁的持有者为当前线程,当该线程再次获取锁时发现它就是锁的持有者,则会把状态值从1变为2,也就是设置可重入次数,而当另外一个线程获取锁时发现自己并不是该锁的持有者就会被放入AQS阻塞队列后挂起。

Semaphore信号量原理

当一个线程通过acquire()方法获取信号量时,会首先看当前信号量个数是否满足需要,不满足则把当前线程放入阻塞队列,如果满足则通过自旋CAS获取信号量。

共享方式下获取资源的流程——void acquireShared(int arg)

当线程调用acquireShared(int arg)获取共享资源时,会首先使用tryAcquireShared尝试获取资源,具体是设置状态变量state的值,成功则直接返回,失败则将当前线程封装为类型为Node.SHARED的Node节点后插入到AQS阻塞队列的尾部,并使用LockSupport.park(this)方法挂起自己。

共享方式下释放资源的流程——boolean releaseShared(int arg)

当一个线程调用releaseShared(int arg)时会尝试使用tryReleaseShared操作释放资源,这里是设置状态变量state的值,然后使用LockSupport.unpark(thread)激活AQS队列里面被阻塞的一个线程(thread)。被激活的线程则使用tryReleaseShared查看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS队列并被挂起。

ReentrantReadWriteLock重写tryAcquireShared和tryReleaseShared方法原理

AQS是锁阻塞和同步器的基础框架,AQS类并没有提供可用的tryAcquireShared和tryReleaseShared方法,tryAcquireShared和tryReleaseShared需要由具体的子类来实现。子类在实现tryAcquireShared和tryReleaseShared时要根据具体场景使用CAS算法尝试修改state状态值,成功则返回true,否则返回false。

重写tryAcquireShared时,首先查看写锁是否被其他线程持有,如果是则直接返回false,否则使用CAS递增state的高16位(在ReentrantReadWriteLock中,state的高16位为获取读锁的次数)。

重写tryReleaseShared时,在内部需要使用CAS算法把当前state值的高16位减1,然后返回true,如果CAS失败则返回false。

基于AQS实现的锁除了需要重写上面介绍的方法外,还需要重写isHeldExclusively方法,来判断锁是被当前线程独占还是被共享。

Interruptibly关键字

独占方式下的void acquire(int arg)和voidacquireInterruptibly(int arg)

共享方式下的void acquireShared(intarg)和void acquireSharedInterruptibly(int arg)

不带Interruptibly关键字的方法

不带Interruptibly关键字的方法的意思是不对中断进行响应,也就是线程在调用不带Interruptibly关键字的方法获取资源时或者获取资源失败被挂起时,其他线程中断了该线程,那么该线程不会因为被中断而抛出异常,它还是继续获取资源或者被挂起,也就是说不对中断进行响应,忽略中断。

带Interruptibly关键字的方法

带Interruptibly关键字的方法要对中断进行响应,也就是线程在调用带Interruptibly关键字的方法获取资源时或者获取资源失败被挂起时,其他线程中断了该线程,那么该线程会抛出InterruptedException异常而返回。

内部类——ConditionObject

AQS有个内部类ConditionObject,用来结合锁实现线程同步。ConditionObject可以直接访问AQS对象内部的变量,比如state状态值和AQS队列。ConditionObject是条件变量,每个条件变量对应一个条件队列(单向链表队列),其用来存放调用条件变量的await方法后被阻塞的线程,如类图所示,这个条件队列的头、尾元素分别为firstWaiter和lastWaiter。

如何维护AQS提供的队列?

入队操作:当一个线程获取锁失败后该线程会被转换为Node节点,然后就会使用enq(final Node node)方法将该节点插入到AQS的阻塞队列。

在第一次循环中,当要在AQS队列尾部插入元素时,AQS队列状态如图6-2中(default)所示。也就是队列头、尾节点都指向null;当执行代码(1)后节点t指向了尾部节点,这时候队列状态如图6-2中(I)所示。这时候t为null,故执行代码(2),使用CAS算法设置一个哨兵节点为头节点,如果CAS设置成功,则让尾部节点也指向哨兵节点,这时候队列状态如图6-2中(II)所示。到现在为止只插入了一个哨兵节点,还需要插入node节点,所以在第二次循环后执行到代码(1),这时候队列状态如图6-2(III)所示;然后执行代码(3)设置node的前驱节点为尾部节点,这时候队列状态如图6-2中(IV)所示;然后通过CAS算法设置node节点为尾部节点,CAS成功后队列状态如图6-2中(V)所示;CAS成功后再设置原来的尾部节点的后驱节点为node,这时候就完成了双向链表的插入,此时队列状态如图6-2中(VI)所示。

AQS条件变量

代码(1)创建了一个独占锁ReentrantLock对象,ReentrantLock是基于AQS实现的锁。

代码(2)使用创建的Lock对象的newCondition()方法创建了一个ConditionObject变量,这个变量就是Lock锁对应的一个条件变量。需要注意的是,一个Lock对象可以创建多个条件变量。

代码(3)首先获取了独占锁

代码(4)则调用了条件变量的await()方法阻塞挂起了当前线程。当其他线程调用条件变量的signal方法时,被阻塞的线程才会从await处返回。需要注意的是,和调用Object的wait方法一样,如果在没有获取到锁前调用了条件变量的await方法则会抛出java.lang.IllegalMonitorStateException异常。

代码(5)则释放了获取的锁。

这里的Lock对象等价于synchronized加上共享变量,调用lock.lock()方法就相当于进入了synchronized块(获取了共享变量的内置锁),调用lock.unLock()方法就相当于退出synchronized块。调用条件变量的await()方法就相当于调用共享变量的wait()方法,调用条件变量的signal方法就相当于调用共享变量的notify()方法。调用条件变量的signalAll()方法就相当于调用共享变量的notifyAll()方法。

一个锁对应一个AQS阻塞队列,对应多个条件变量,每个条件变量有自己的一个条件队列。

基于AQS实现的不可重入的独占锁

package test;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
//基于AQS实现的不可重入的独占锁
public class NonReentrantLock implements Lock,java.io.Serializable{
	private static class Sync extends AbstractQueuedSynchronizer{
		//是否锁已被持有
		protected boolean isHeldExclusively(){
			return getState()==1;
		}
		//如果state为0 则尝试获取锁
		public boolean tryAcquire(int acquires) {
			assert acquires == 1;
			if(compareAndSetState(0,1)) {
				setExclusiveOwnerThread(Thread.currentThread());
				return true;
			}
			return false;
		}
		//尝试释放锁,设置state为0
		protected boolean tryRelease(int releases) {
			assert releases == 1;
			if(getState() == 0) {
				throw new IllegalMonitorStateException();
			}
			setExclusiveOwnerThread(null);
			setState(0);
			return true;
		}
		//提供条件变量接口
		Condition newCondition() {
			return new ConditionObject();
		}
	}
    //创建一个Sync来做具体的工作
	private final Sync sync = new Sync();
	@Override
	public void lock() {
		sync.acquire(1);
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {
		sync.acquireInterruptibly(1);
	}

	@Override
	public boolean tryLock() {
		return sync.tryAcquire(1);
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		return sync.tryAcquireNanos(1,unit.toNanos(time));
	}

	@Override
	public void unlock() {
		sync.tryRelease(1);
	}

	@Override
	public Condition newCondition() {
		return sync.newCondition();
	}
	public boolean isLocked() {
		return sync.isHeldExclusively();
	}

}

使用自定义锁实现生产—消费模型

package test;

import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Condition;

public class Producer_Comsumer {
    final static NonReentrantLock lock = new NonReentrantLock();
    final static Condition notFull = lock.newCondition();
    final static Condition notEmpty = lock.newCondition();
    final static Queue<String> queue = new LinkedBlockingQueue<String>();
    final static int queueSize = 10;
    public static void main(String[] args) {
    	Thread producer = new Thread(new Runnable() {
			@Override
			public void run() {
				//获取独占锁
				lock.lock();
				try {
					//(1)如果队列满了,则等待
					while(queue.size()==queueSize) {
						notEmpty.await();
					}
					//(2)添加元素到队列
					queue.add("ele");
					//(3)唤醒消费线程
					notFull.signalAll();
				}catch(Exception e) {
					e.printStackTrace();
				}finally{
					//释放锁
					lock.unlock();
				}
				
			}
    		
    	});
    	Thread consumer = new Thread(new Runnable() {
			@Override
			public void run() {
				//获取独占锁
				lock.lock();
				try {
					//(1)如果队列空,则等待
					while(0==queue.size()) {
						notFull.await();
					}
					//(2)消费一个元素
					String ele = queue.poll();
					//(3)唤醒生产线程
					notEmpty.signalAll();
				}catch(Exception e) {
					e.printStackTrace();
				}finally{
					//释放锁
					lock.unlock();
				}
				
			}
    		
    	});
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值