AQS的使用和reentrantlock

AQS的使用和reentrantlock

AQS概述

AQS是一个同步器,全称是AbstractQueuedSynchronizer类。

使用方法:子类继承AQS,然后重写tryAcquire、tryRelease、isHeldExclusively(如果是共享模式实现tryAcquireShared和tryReleaseShared方法),然后将其作为内部类,外部类定义unlock和lock方法并调用acquire和release方法即可(共享模式调用acquireShared和releaseShared)。

线程协调场景主要分为两类,一个线程在进行操作时不允许其他线程操作,即独占模式,还有允许多个线程操作的情况,称为共享模式。(独占和共享两种模式可以两个同时实现)

一个关键变量:内部封装了一个volatile修饰的int类型的字段state,代表当前同步状态。

定义了访问该字段的几个方法getState()、setState(val)、compareAndSetState(expect, update)

独占模式

独占模式下保证线程同步的操作是这样的,将state设为0,当某个线程要独占时都需要先判断state是不是0,如果不是就进入阻塞状态等待,如果是0就把state设置为1。然后进行操作,实现这个操作需要设置3种操作:分别是尝试获取同步状态、释放同步状态、判断是否有线程独占。

这三种操作分别对应下面三个方法tryAcquire(tryAcquireNanos方法加了超时限制,超时就自动视为获取失败)、tryRelease、isHeldExclusively。

public class PlainLock {

	private static class Sync extends AbstractQueuedSynchronizer{

		@Override
		protected boolean tryAcquire(int arg) {
			// TODO Auto-generated method stub
			return compareAndSetState(0, 1);
		}

		@Override
		protected boolean tryRelease(int arg) {
			// TODO Auto-generated method stub
			setState(0);
			return true;
		}

		@Override
		protected boolean isHeldExclusively() {
			// TODO Auto-generated method stub
			return getState() == 1;
		}
	}
	
	private Sync sync = new Sync();
	public void lock() {
		sync.acquire(1);
	}
	
	public void unlock() {
		sync.release(1);
	}
}

共享模式

当同步器使用共享模式时,区别在于允许多个线程持有资源,这个数量就是state的初始值,共享模式下需要定义两个方法tryAcquireShared(val)、tryReleaseShared(val),也是尝试获得和释放同步状态,尝试获得时返回值为int数,返回一个非负数代表获得同步状态成功(需要在aqs的构造方法中设置setState方法来确定可以同时并发的线程),然后重定义lock和unlock方法内部使用acquireShared和releaseShared方法。

//保证不超过两个线程同时访问的锁
public class DoubleLock {

	private class Sync extends AbstractQueuedSynchronizer{
		
		public Sync() {
			super();
			setState(2);
		}
		
		//共享模式下需要一开始在构造方法里设置state的值
		//然后在重写tryAcquireShared和tryReleaseShared方法
		
		//该返回值大于等于0说明获取同步状态成功,否则加入同步队列
		@Override
		protected int tryAcquireShared(int arg) {
			// TODO Auto-generated method stub
			while(true) {
				int now = getState();
				int next = getState() - arg;
				if(compareAndSetState(now, next)) {
					return next;
				}
			}
		}

		@Override
		protected boolean tryReleaseShared(int arg) {
			// TODO Auto-generated method stub
			while(true) {
				int now = getState();
				int next = getState() + arg;
				if(compareAndSetState(now, next)) {
					return true;
				}
			}
		}
	}
	
	private Sync sy = new Sync();
	
	public void lock() {
		sy.acquireShared(1);
	}
	
	public void unlock() {
		sy.releaseShared(1);
	}	
}

acquire和release方法

AQS内部维护了一个node类,node中有两个指针和一个线程Thread,AQS内部维护了两个node,分别是node队列的头和尾节点。

acquire(args)方法就是获取同步状态,如果成功就返回,如果失败就将本线程加入同步队列。内部首先调用tryAcquire(args)方法尝试获取同步状态,如果失败之后会调用addWaiter和acquireQueued方法封装node插入同步队列并再次尝试获取,失败后会检查队列中node的状态,修改node的状态并阻塞。

(还可以使用acquireInterruptibly方法,它是可中断的,如果没获取到而阻塞,其他线程中断了该线程就会抛出interruptedEx异常)

release(args)方法就是释放同步状态,会调用tryrelease方法,如果成功了就唤醒同步队列的下一个节点,并释放头节点。

reentrantlock的实现

Reentrantlock内部定义了一个AQS的子类,每次new一个Reentrantlock就相当于new了一个AQS,内部封装着一个同步队列,AQS内部还有一个ConditionObject的内部类,这个内部类内部维护了两个node类型变量,相当于维护了一个队列,AQS中的队列和这个ConditionObject中的队列用的是同一个node。

Reentrantlock有一个方法newCondition,这个方法会返回一个ConditionObject类,也就是说每调用newCondition方法一次,就会构建出一个队列(这个等待队列是单向链表,而aqs同步队列是双向链表)。

ConditionObject类实现了condition接口,这个接口内部有await和signal方法,await方法执行后,该线程对应的node就会被放入对应condition的队列中,从AQS同步队列中取下(当取下node后AQS同步队列为空时那么最后这一个node就不会被取下),同时改变node的等待状态,signal方法负责唤醒线程,就是将对应condition中的node取下再放回AQS同步队列中去,同时改变node的等待状态。

Reentrantlock这种可以创建多个condition,也就是创建了多个等待队列,可以实现特定的唤醒,使一个显式锁对应多个等待队列的效果。

posted @ 2019-09-17 10:33  勇闯8  阅读(560)  评论(0编辑  收藏  举报