[docs fix]aqs 模板方法模式描述

This commit is contained in:
guide 2022-02-07 19:18:50 +08:00
parent afee8d8a38
commit 274848094e
2 changed files with 21 additions and 21 deletions

View File

@ -202,36 +202,34 @@ final boolean nonfairTryAcquire(int acquires) {
1. 使用者继承 `AbstractQueuedSynchronizer` 并重写指定的方法。(这些重写方法很简单,无非是对于共享资源 state 的获取和释放)
2. 将 AQS 组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用,下面简单的给大家介绍一下模板方法模式,模板方法模式是一个很容易理解的设计模式之一
这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。
> 模板方法模式是基于”继承“的,主要是为了在不改变模板结构的前提下在子类中重新定义模板中的内容以实现复用代码。
>
> 举个很简单的例子假如我们要去一个地方的步骤是:购票 `buyTicket()`->安检 `securityCheck()`->乘坐某某工具回家 `ride()` ->到达目的地 `arrive()`。我们可能乘坐不同的交通工具回家比如飞机或者火车,所以除了`ride()`方法,其他方法的实现几乎相同。我们可以定义一个包含了这些方法的抽象类,然后用户根据自己的需要继承该抽象类然后修改 `ride()`方法。
**AQS 使用了模板方法模式,自定义同步器时需要重写下面几个 AQS 提供的模板方法:**
**AQS 使用了模板方法模式,自定义同步器时需要重写下面几个 AQS 提供的钩子方法:**
```java
isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它
tryAcquire(int)//独占方式。尝试获取资源成功则返回true失败则返回false。
tryRelease(int)//独占方式。尝试释放资源成功则返回true失败则返回false
tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败0表示成功但没有剩余可用资源正数表示成功且有剩余资源
tryReleaseShared(int)//共享方式。尝试释放资源成功则返回true失败则返回false
protected boolean tryAcquire(int)//独占方式。尝试获取资源成功则返回true失败则返回false。
protected boolean tryRelease(int)//独占方式。尝试释放资源成功则返回true失败则返回false。
protected boolean tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败0表示成功但没有剩余可用资源正数表示成功且有剩余资源
protected boolean tryReleaseShared(int)//共享方式。尝试释放资源成功则返回true失败则返回false
protected boolean isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它
```
默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的并且通常应该简短而不是阻塞。AQS 类中的其他方法都是 final ,所以无法被其他类使用,只有这几个方法可以被其他类使用
**什么是钩子方法呢?** 钩子方法是一种被声明在抽象类中的方法,一般使用 `protected` 关键字修饰,它可以是空方法(由子类实现),也可以是默认实现的方法。模板设计模式通过钩子方法控制固定步骤的实现
`ReentrantLock` 为例state 初始化为 0表示未锁定状态。A 线程 `lock()` 时,会调用 `tryAcquire()`独占该锁并将 state+1。此后其他线程再 `tryAcquire()` 时就会失败,直到 A 线程 unlock()到 state=0即释放锁为止其它线程才有机会获取该锁。当然释放锁之前A 线程自己是可以重复获取此锁的state 会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证 state 是能回到零态的
篇幅问题,这里就不详细介绍模板方法模式了,不太了解的小伙伴可以看看这篇文章:[用Java8 改造后的模板方法模式真的是 yyds!](https://mp.weixin.qq.com/s/zpScSCktFpnSWHWIQem2jg)
再以 `CountDownLatch` 以例,任务分为 N 个子线程去执行state 也初始化为 N也可以不初始化为 N不初始化为 N,state 减到 0 也会从 await()返回)。这 N 个子线程是并行执行的,每个子线程执行完后 `countDown()` 一次state 会 CAS(Compare and Swap)减 1。等到 `state=0`,会 `unpark()` 主调用线程,然后主调用线程就会从 `await()` 函数返回,继续后余动作
除了上面提到的钩子方法之外AQS 类中的其他方法都是 `final` ,所以无法被其他类重写
所以 `CountDownLatch` 可以做倒计数器,减到 0 后唤醒的线程可以对线程池进行处理,比如关闭线程池。
`ReentrantLock` 为例state 初始化为 0表示未锁定状态。A 线程 `lock()` 时,会调用 `tryAcquire()` 独占该锁并将 `state+1` 。此后,其他线程再 `tryAcquire()` 时就会失败,直到 A 线程 `unlock()``state=`0即释放锁为止其它线程才有机会获取该锁。当然释放锁之前A 线程自己是可以重复获取此锁的state 会累加),这就是可重入的概念。但要注意,获取多少次就要释放多少次,这样才能保证 state 是能回到零态的。
再以 `CountDownLatch` 以例,任务分为 N 个子线程去执行state 也初始化为 N注意 N 要与线程个数一致)。这 N 个子线程是并行执行的,每个子线程执行完后` countDown()` 一次state 会 CAS(Compare and Swap) 减 1。等到所有子线程都执行完后(即 `state=0` ),会 `unpark()` 主调用线程,然后主调用线程就会从 `await()` 函数返回,继续后余动作。
一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但 AQS 也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。
推荐两篇 AQS 原理和相关源码分析的文章:
- [Java 并发之 AQS 详解](https://www.cnblogs.com/waterystone/p/4920797.html)
- [Java 并发包基石-AQS 详解](https://www.cnblogs.com/chengxiao/p/7141160.html)
- [Java并发之AQS详解](https://www.cnblogs.com/waterystone/p/4920797.html)
- [Java并发包基石-AQS详解](https://www.cnblogs.com/chengxiao/p/7141160.html)
## Semaphore(信号量)

View File

@ -942,7 +942,7 @@ protected final boolean compareAndSetState(int expect, int update) {
这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。
**AQS 使用了模板方法模式,自定义同步器时需要重写下面几个 AQS 提供的模板方法:**
**AQS 使用了模板方法模式,自定义同步器时需要重写下面几个 AQS 提供的钩子方法:**
```java
protected boolean tryAcquire(int)//独占方式。尝试获取资源成功则返回true失败则返回false。
@ -952,11 +952,13 @@ protected boolean tryReleaseShared(int)//共享方式。尝试释放资源,成
protected boolean isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
```
默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的并且通常应该简短而不是阻塞。AQS 类中的其他方法都是 final ,所以无法被其他类使用,只有这几个方法可以被其他类使用
**什么是钩子方法呢?** 钩子方法是一种被声明在抽象类中的方法,它可以是空方法(由子类实现),也可以是默认实现的方法。模板设计模式通过钩子方法控制固定步骤的实现
以 ReentrantLock 为例state 初始化为 0表示未锁定状态。A 线程 lock()时,会调用 tryAcquire()独占该锁并将 state+1。此后其他线程再 tryAcquire()时就会失败,直到 A 线程 unlock()到 state=0即释放锁为止其它线程才有机会获取该锁。当然释放锁之前A 线程自己是可以重复获取此锁的state 会累加),这就是可重入的概念。但要注意,获取多少次就要释放多少次,这样才能保证 state 是能回到零态的
除了上面提到的钩子方法之外AQS 类中的其他方法都是 `final` ,所以无法被其他类重写
再以 `CountDownLatch` 以例,任务分为 N 个子线程去执行state 也初始化为 N注意 N 要与线程个数一致)。这 N 个子线程是并行执行的,每个子线程执行完后` countDown()` 一次state 会 CAS(Compare and Swap)减 1。等到所有子线程都执行完后(即 state=0),会 unpark()主调用线程,然后主调用线程就会从 `await()` 函数返回,继续后余动作。
`ReentrantLock` 为例state 初始化为 0表示未锁定状态。A 线程 `lock()` 时,会调用 `tryAcquire()` 独占该锁并将 `state+1` 。此后,其他线程再 `tryAcquire()` 时就会失败,直到 A 线程 `unlock()``state=`0即释放锁为止其它线程才有机会获取该锁。当然释放锁之前A 线程自己是可以重复获取此锁的state 会累加),这就是可重入的概念。但要注意,获取多少次就要释放多少次,这样才能保证 state 是能回到零态的。
再以 `CountDownLatch` 以例,任务分为 N 个子线程去执行state 也初始化为 N注意 N 要与线程个数一致)。这 N 个子线程是并行执行的,每个子线程执行完后` countDown()` 一次state 会 CAS(Compare and Swap) 减 1。等到所有子线程都执行完后(即 `state=0` ),会 `unpark()` 主调用线程,然后主调用线程就会从 `await()` 函数返回,继续后余动作。
一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但 AQS 也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。