Lock简介与初识AQS

Provides a framework for implementing blocking locks and related

synchronizers (semaphores, events, etc) that rely on

first-in-first-out (FIFO) wait queues. This class is designed to

be a useful basis for most kinds of synchronizers that rely on a

single atomic {@code int} value to represent state. Subclasses

must define the protected methods that change this state, and which

define what that state means in terms of this object being acquired

or released. Given these, the other methods in this class carry

out all queuing and blocking mechanics. Subclasses can maintain

other state fields, but only the atomically updated {@code int}

value manipulated using methods {@link #getState}, {@link

#setState} and {@link #compareAndSetState} is tracked with respect

to synchronization.

Subclasses should be defined as non-public internal helper

classes that are used to implement the synchronization properties

of their enclosing class. Class

{@code AbstractQueuedSynchronizer} does not implement any

synchronization interface. Instead it defines methods such as

{@link #acquireInterruptibly} that can be invoked as

appropriate by concrete locks and related synchronizers to

implement their public methods.

同步器是用来构建锁和其他同步组件的基础框架,它的实现主要依赖一个int成员变量来表示同步状态以及通过一个FIFO队列构成等待队列。它的子类必须重写AQS的几个protected修饰的用来改变同步状态的方法,其他方法主要是实现了排队和阻塞机制。状态的更新使用getState,setState以及compareAndSetState这三个方法

子类被推荐定义为自定义同步组件的静态内部类,同步器自身没有实现任何同步接口,它仅仅是定义了若干同步状态的获取和释放方法来供自定义同步组件的使用,同步器既支持独占式获取同步状态,也可以支持共享式获取同步状态,这样就可以方便的实现不同类型的同步组件。

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

[](

)AQS的模板方法设计模式

AQS的设计是使用模板方法设计模式,它将一些方法开放给子类进行重写,而同步器给同步组件所提供模板方法又会重新调用被子类所重写的方法。举个例子,AQS中需要重写的方法tryAcquire:

AQS中的tryAcquire()方法:

protected boolean tryAcquire(int arg) {

throw new UnsupportedOperationException();

}

ReentrantLock中NonfairSync(继承AQS)会重写该方法为:

protected final boolean tryAcquire(int acquires) {

return nonfairTryAcquire(acquires);

}

而AQS中的模板方法acquire():

public final void acquire(int arg) {

if (!tryAcquire(arg) &&

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

selfInterrupt();

}

会调用tryAcquire方法,而此时当继承AQS的NonfairSync调用模板方法acquire时就会调用已经被NonfairSync重写的tryAcquire方法。这就是使用AQS的方式,在弄懂这点后对lock的实现理解有很大的提升。可以归纳总结为这么几点:

  1. 同步组件(这里不仅仅指锁,还包括CountDownLatch等)的实现依赖于同步器AQS,在同步组件实现中,使用AQS的方式被推荐定义继承AQS的静态内存类;

  2. AQS采用模板方法进行设计,AQS的protected修饰的方法需要由继承AQS的子类进行重写实现,当调用AQS的子类的方法时就会调用被重写的方法;

  3. AQS负责同步状态的管理,线程的排队,等待和唤醒这些底层操作,而Lock等同步组件主要专注于实现同步语义;

  4. 在重写AQS的方式时,使用AQS提供的getState(),setState(),compareAndSetState()方法进行修改同步状态

AQS可重写的方法如下图(摘自《Java并发编程的艺术》一书):

AQS可重写的方法.png

在实现同步组件时AQS提供的模板方法如下图:

AQS提供的模板方法.png

AQS提供的模板方法可以分为3类:

  1. 独占式获取与释放同步状态;

  2. 共享式获取与释放同步状态;

  3. 查询同步队列中等待线程情况;

同步组件通过AQS提供的模板方法实现自己的同步语义。

[](

)一个例子


下面使用一个例子来进一步理解下AQS的使用。这个例子也是来源于AQS源码中的example。

class Mutex implements Lock, java.io.Serializable {

// Our internal helper class

// 继承AQS的静态内存类

// 重写方法

private static class Sync extends AbstractQueuedSynchronizer {

// Reports whether in locked state

protected boolean isHeldExclusively() {

return getState() == 1;

}

// Acquires the lock if state is zero

public boolean tryAcquire(int acquires) {

assert acquires == 1; // Otherwise unused

if (compareAndSetState(0, 1)) {

setExclusiveOwnerThread(Thread.currentThread());

return true;

}

return false;

}

// Releases the lock by setting state to zero

protected boolean tryRelease(int releases) {

assert releases == 1; // Otherwise unused

if (getState() == 0) throw new IllegalMonitorStateException();

setExclusiveOwnerThread(null);

setState(0);

return true;

}

// Provides a Condition

Condition newCondition() {

return new ConditionObject();

}

// Deserializes properly

private void readObject(ObjectInputStream s)

throws IOException, ClassNotFoundException {

s.defaultReadObject();

setState(0); // reset to unlocked state

}

}

// The sync object does all the hard work. We just forward to it.

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));

}

}

MutexDemo:

public class MutextDemo {

private static Mutex mutex = new Mutex();

public static void main(String[] args) {

for (int i = 0; i < 10; i++) {

Thread thread = new Thread(() -> {

mutex.lock();

try {

Thread.sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

mutex.unlock();

}

});

thread.start();

}

}

}

执行情况:

mutex的执行情况.png

上面的这个例子实现了独占锁的语义,在同一个时刻只允许一个线程占有锁。MutexDemo新建了10个线程,分别睡眠3s。从执行情况也可以看出来当前Thread-6正在执行占有锁而其他Thread-7,Thread-8等线程处于WAIT状态。按照推荐的方式,Mutex定义了一个继承AQS的静态内部类Sync,并且重写了AQS的tryAcquire等等方法,而对state的更新也是利用了setState(),getState(),compareAndSetState()这三个方法。在实现实现lock接口中的方法也只是调用了AQS提供的模板方法(因为Sync继承AQS)。从这个例子就可以很清楚的看出来,在同步组件的实现上主要是利用了AQS,而AQS“屏蔽”了同步状态的修改,线程排队等底层实现,通过AQS的模板方法可以很方便的给同步组件的实现者进行调用。而针对用户来说,只需要调用同步组件提供的方法来实现并发编程即可。同时在新建一个同步组件时需要把握的两个关键点是:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值