Java并发之AQS:简介

        队列同步器AbstractQueuedSynchronizer(AQS,简称同步器),是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成线程的排队工作,理解了同步器,就可以更加深入的理解Java并发包中其他的并发组件,并发包的作者(Doug Lea)期望它能成为实现大部分同步需求的基础。

                                                                                                         -------《Java 并发编程的艺术》

——————————————我是分割线———————————————————————————

这篇博客中首先对AQS作一个介绍,然后利用API中的一个示例深入理解一下。

什么是同步器?

引用API中对AQS的定义先引入这个概念:

       为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。此类的设计目标是成为依靠单个原子int值来表示状态的大多数同步器的一个有用基础。子类必须定义更改此状态的受保护方法,并定义哪种状态对于此对象意味着被获取或被释放。假定这些条件之后,此类中的其他方法就可以实现所有排队和阻塞机制。子类可以维护其他状态字段,但只是为了获得同步而只追踪使用getState()、setState(int newState)、compareAndSetState(int except,int update)方法来操作以原子方式更新的int值。

       此类支持默认的独占模式和共享模式之一,或者二者都支持。处于独占模式下时,其他线程试图获取该锁将无法取得成功。在共享模式下,多个线程获取某个锁可能(但不是一定)会获得成功。此类并不“了解”这些不同,除了机械地意识到当在共享模式下成功获取某一锁时,下一个等待线程(如果存在)也必须确定自己是否可以成功获取该锁。处于不同模式下的等待线程可以共享相同的 FIFO 队列。通常,实现子类只支持其中一种模式,但两种模式都可以在(例如)ReadWriteLock中发挥作用。只支持独占模式或者只支持共享模式的子类不必定义支持未使用模式的方法。 

划重点:

1、同步器的主要实现方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,也就是利用上面提到的单个原子int值来管理同步状态;

2、在子类重写同步器指定的方法时,需要使用同步器提供的三个方法:getState()、setState(int newState)、compareAndSetState(int except,int update)来访问或修改同步状态,当然除了这三个管理同步状态的方法外还有很多其他方法,下面会介绍到。要注意哦,这里对同步状态修改时,用到了CAS,这是为了保证状态的改变是安全的;

3、API里提到,建立将子类定义为自定义同步组件中的静态内部类;

4、同步器定义了若干同步状态的获取和释放方法来供同步组件使用,同步器即支持独占式地获取同步状态,也可以支持共享式的获取同步状态。

同步器是实现锁(或其他任意同步组件)的关键,在锁中聚合同步器,利用同步器实现锁的语义。可以这样理解两者的关系:锁是面向使用者的,它定义了用户和锁交互的接口,例如可以允许两个线程并行访问,隐藏了实现细节;同步器面向的是锁的是闲着,它简化了锁的实现方式,屏蔽了锁实现过程中的同步状态管理、线程的排队、线程等待与唤醒等底层操作。

同步器中提供的一些方法:

  • boolean tryAcquire(int arg):独占式获取同步状态,需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态;
  • boolean  tryRelease(int arg):独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态;
  • int tryAcquireShared(int arg):共享式获取同步状态,返回值大于等于0,表示获取成功,否则获取失败;
  • boolean  tryReleaseShared(int arg):共享式释放同步状态;
  • boolean  isHeldExclusively():当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程所独占;

以上方法为同步器提供的重写方法,后面方法均为模版方法,自定义同步组件时使用同步器提供的模版方法来实现自己的同步语义,在示例中会体现哦。

  • void acquire(int arg):独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用可重写的tryAcquire(int arg)方法;
  • void acquireInterruptibly(int arg):与acquire(int arg)相同,但是该方法响应中断,当前线程未获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException并返回;
  • boolean tryAcquireNanos(int arg,long nanos):超时获取同步状态,如果当前线程在nanos时间内没有获取到同步状态,那么将会返回false,如果获取到则返回true;
  • void acquireShared(int arg):共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;
  • void acquireSharedInterruptibly(int arg):与acquireShared(int arg)相同,该方法响应中断;
  • boolean tryAcquireSharedNanos(int arg, long nanosTimeout):在acquireSharedInterruptibly(int arg)的基础上增加超时限制;
  • boolean release(int arg):独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒;
  • boolean releaseShared(int arg):共享式释放同步状态;

    上面release方法中提到了节点,在后续的同步队列实现中会做深入分析,这里大概介绍一下:

AQS依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程及等待状态等信息构造为一个节点(Node)并将其加入同步队列中,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获同步状态。

       让我们通过一个独占锁的示例来深入了解一个同步器的工作原理。顾名思义,独占锁就是同一时刻只能由一个线程获取到锁,而其他获取锁的线程只能处于同步队列中,只有获取锁的线程释放了锁,后继的线程才能够获得锁。

/**
 * @author Administrator
 *
 */
public class Mutex implements Lock {
	//静态内部类,自定义同步器
	private static class Sync extends AbstractQueuedSynchronizer{
		//是否处于占用状态,1表示占用,0表示释放
		protected boolean isHeldExclusively() {
			return getState() == 1;
		}
		//当状态为0时获取锁
		public boolean tryAcquire(int acquires) {
			if(compareAndSetState(0,1)) {
				setExclusiveOwnerThread(Thread.currentThread());
				return true;
			}
			return false;
		}
		//释放锁,并将状态设为0
		protected boolean tryRelease(int releases) {
			if(getState() == 0) {
				throw new IllegalMonitorStateException();
			}
			setExclusiveOwnerThread(null);
			setState(0);
			return true;
		}
		//返回一个condition,每个condition包含一个condition队列
		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.release(1);
	}

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

}

       在上面的例子中,独占锁Mutex是一个自定义同步组件,它在同一时刻只允许一个线程占有锁。Mutex中定义了一个静态内部类,该内部类继承了AQS并实现了独占式获取和释放同步状态。在tryAcquire(int acquires)方法中,如果经过CAS设置成功(同步状态设为1),则代表获取了同步状态,而在tryRelease(int releases)中只是将同步状态重置为0,用户使用Mutex时并不会直接与内部同步器打交交道,而是调用Mutex提供的方法,在Mutex的实现中,以获取锁的lock()为例,只需要在方法实现中调用同步器的模版方法acquire(int args)即可。

       后续会就同步队列、独占式同步状态的获取和释放、共享式同步状态的获取和释放以及超时获取同步状态等同步器关键部分从实现角度进行分析。

———————————————没错我又来了——————————————————————

参考:

方腾飞:《Java 并发编程的艺术》

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值