队列同步器

队列同步器(AbstractQueuedSynchronizer)是构建锁和其他同步组件的基础框架,使用int成员变量表示同步状态,通过FIFO队列完成线程排队工作。本文详细介绍了队列同步器的工作原理及其实现方式。

1.队列同步器简介

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

    同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的3个方法(getState()、setState(int newState)和compareAndSetState(int expect,int update))来进行操作,因为它们能够保证状态的改变是安全的。子类推荐被定义为自定义同步组件的静态内部类,同步器自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器既可以支持独占式地获取同步状态,也可以支持共享式地获取同步状态,这样就可以方便实现不同类型的同步组件(ReentrantLock、ReentrantReadWriteLock和CountDownLatch等)。

     同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。可以这样理解二者之间的关系:锁是面向使用者的,它定义了使用者与锁交互的接口(比如可以允许两个线程并行访问),隐藏了实现细节;同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。锁和同步器很好地隔离了使用者和实现者所需关注的领域。

2.同步器的接口与实例

同步器的设计是基于模板方法模式的,也就是说,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。重写同步器指定的方法时,需要使用同步器提供的如下3个方法来访问或修改同步状态。
·getState():获取当前同步状态。
·setState(int newState):设置当前同步状态。
·compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性。
同步器可重写的方法与描述如下表所示:

同步器可重写的方法
                          方法名称                                                                   描述
protected boolean tryAcquire(int arg) 

独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态十分

符合预期,然后再用CAS设置同步状态

protected boolean tryRelease(int arg)独占式释放同步状态
protected int tryAcquireShared(int arg)共享式获取同步状态,返回大于等于0的值,表示获取成功,反之,获取失败
protected boolean tryReleaseShared(int arg) 共享式释放同步状态
protected boolean isHeldExclusively()当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程独占

实现自定义同步组件时,将会调用同步器提供的模板方法,这些(部分)模板方法与描述如下表所示:

同步器提供的模板方法
                         方法名称                                                  描述
void acquire(int arg) 

独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用重写的tryAcquire(int arg) 方法

void acquireInterruptibly(int arg)

与 acquire(int arg)相同,但是该方法响应中断,当前线程未获取到同步状态而进入同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException并返回

boolean try Acquire Nanos(int arg,long nanos)

在 acquireInterruptibly (int arg)基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,那么将会返回 false,如果获取到了返回true

void acquire Shared (int arg)

共享式的获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式获取的主要区别是在同一时刻可以有多个线程获取到同步状态

void acquireSharedIntermuptibly(in arg)

与 acquireShared(int arg)相同,该方法响应中断

boolean tryAcquireSharedNanos(int arg,long nanos)

在 acquireSharedInterruptibly (int arg)基础上增加了超时限制

boolean release(int arg)

独占式的释放同步状态,该方法会在释放同步状态之后,将同步队列中第一

个节点包含的线程唤醒

boolean releaseShared(int arg)共享式的释放同步状态

Collection<Thread> getQueuedThreads()

获取等待在同步队列上的线程集合

      同步器提供的模板方法基本上分为3类:独占式获取与释放同步状态、共享式获取与释放同步状态和查询同步队列中的等待线程情况。自定义同步组件将使用同步器提供的模板方法来实现自己的同步语义。
      只有掌握了同步器的工作原理才能更加深入地理解并发包中其他的并发组件,所以下面
通过一个独占锁的示例来深入了解一下同步器的工作原理。
      顾名思义,独占锁就是在同一时刻只能有一个线程获取到锁,而其他获取锁的线程只能处于同步队列中等待,只有获取锁的线程释放了锁,后继的线程才能够获取锁,如代码清单所示:

public class Mutex implements Lock {
	// 静态内部类,自定义同步器
	private static class Sync extends AbstractQueuedSynchronizer {
		// 是否处于占用状态
		protected boolean isHeldExclusively() {
			return getState() == 1;
		}
		// 当状态为0的时候获取锁
		public boolean tryAcquire(int acquires) {
			//尝试将状态设置为acquires,无锁状态下进行
			if (compareAndSetState(0,acquires)) {
				setExclusiveOwnerThread(Thread.currentThread());
				return true;
			}
			return false;
		}
		// 释放锁,将状态设置为0
		protected boolean tryRelease(int releases) {
			//如果当前状态无锁,抛出IllegalMonitorStateException异常
			if (getState() == 0) throw new IllegalMonitorStateException();
			//将锁指向的线程设置为空
			setExclusiveOwnerThread(null);
			//当前状态设置为0,释放锁
			setState(0);
			return true;
		}
		// 返回一个Condition,每个condition都包含了一个condition队列
		Condition newCondition() { 
			return new ConditionObject(); 
		}
	}
	// 仅需要将操作代理到Sync上即可
	private final Sync sync = new Sync();
	
	public void lock() { 
		sync.acquire(1); 
	}
	
	public boolean tryLock() { 
		return sync.tryAcquire(1); 
	}
	
	public void unlock() {
		sync.release(1); 
	}
	
	public Condition newCondition() { 
		return sync.newCondition(); 
	}
	
	public boolean isLocked() { 
		return sync.isHeldExclusively(); 
	}
	
	public boolean hasQueuedThreads() { 
		return sync.hasQueuedThreads(); 
	}
	
	public void lockInterruptibly() throws InterruptedException {
		sync.acquireInterruptibly(1);
	}
	
	public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
		return sync.tryAcquireNanos(1, unit.toNanos(timeout));
	}
}

上述示例中,独占锁Mutex是一个自定义同步组件,它在同一时刻只允许一个线程占有锁。Mutex中定义了一个静态内部类,该内部类继承了同步器并实现了独占式获取和释放同步状态。在tryAcquire(int acquires)方法中,如果经过CAS设置成功(同步状态设置为1),则代表获取了同步状态,而在tryRelease(int releases)方法中只是将同步状态重置为0。用户使用Mutex时并不会直接和内部同步器的实现打交道,而是调用Mutex提供的方法,在Mutex的实现中,以获取锁的lock()方法为例,只需要在方法实现中调用同步器的模板方法acquire(int args)即可,当前线程调用该方法获取同步状态失败后会被加入到同步队列中等待,这样就大大降低了实现一个可靠自定义同步组件的门槛。

小结:同步器中的模板方法,如acquire等方法,会调用需要重写的方法,如tryAcquire。如果需要实现自定义的并发类,就要重写上表中的可重写的方法。

PS:总结自《Java并发编程的艺术》——方腾飞

### 抽象队列同步器 AQS 的原理与实现 #### 1. AQS 的定义与作用 AQS(AbstractQueuedSynchronizer)是 Java 并发包 `java.util.concurrent` 中的一个核心组件,它提供了一种框架化的机制来构建锁和其他同步工具。AQS 使用了一个基于 FIFO 的双向链表队列来管理线程之间的竞争关系,并通过状态变量控制线程的同步行为[^1]。 #### 2. 核心组成部分 ##### (1)状态变量(state) AQS 维护一个名为 `state` 的整型变量,用来表示同步状态。不同的子类可以通过原子操作修改这个变量以反映资源的占有情况。例如,在 ReentrantLock 中,`state` 表示当前锁被持有的次数;而在 Semaphore 中,`state` 则代表剩余信号量的数量[^3]。 ##### (2)CLH 队列 为了高效地处理大量线程的竞争,AQS 内部采用 CLH(Craig, Landin, and Hagersten locks)变体形式的队列结构。当一个线程试图获取锁失败时,会被封装成节点加入到该队列中等待下一次机会。每个节点包含了指向前后相邻节点的指针以及一些标志位用于记录自身的状况[^4]。 #### 3. 主要方法分类 根据职责划分,AQS 定义了一系列模板方法供开发者覆盖实现: - **独占模式 vs 共享模式** - 独占模式意味着同一时刻只有一个线程能成功获得许可执行关键路径上的代码片段。 - 共享模式允许多个线程同时持有相同的权限级别而不违反约束条件。 - **tryAcquire/tryRelease 系列函数** 这些是非公平版本下的尝试性接口,默认情况下不考虑排队顺序直接抢夺资源使用权。如果调用者满足准入资格则返回 true ,否则 false 。对于释放动作而言则是更新内部计数器并将可能唤醒下一个候选者[^2]。 - **acquire/acquireShared 方法族** 正常途径进入临界区的方式分为两类:一是完全遵循既定规则逐级向上申请直至达成目的为止;二是允许一定程度上的投机取巧即所谓的“乐观锁定策略”。 - **release/releaseShared 函数组** 类似于上面提到的内容只不过方向反转过来而已——前者是从拥有权角度出发逐步削减直到彻底放弃掌控权;后者强调集体利益最大化原则之下共同退让部分权益给后来者享用[^2]。 #### 4. 锁定机制详解 以下是有关如何运用 AQS 构建自定义同步原语的一些简单例子: ```java public class MyMutex extends AbstractQueuedSynchronizer { protected boolean tryAcquire(int acquires) { if (compareAndSetState(0, 1)) { // CAS 更新 state 值 setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } protected boolean tryRelease(int releases) { if (getState() == 0) throw new IllegalMonitorStateException(); setState(0); // 清零恢复初始态 setExclusiveOwnerThread(null); // 解绑当前所有者信息 return true; // 返回值决定是否通知后续待命成员 } public void lock() { acquire(1); } public void unlock() { release(1); } } ``` 上述代码展示了怎样借助 AQS 来模拟互斥锁的行为特性。其中重点在于重写两个虚基类提供的纯虚函数分别对应加锁解锁两阶段的操作逻辑[^3]。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值