【Beautiful JUC Part.10】AQS 并发灵魂人物

【Beautiful JUC Part.10】AQS 并发灵魂人物

一、为什么需要AQS?

image-20220212170217636

image-20220212170256335

二、Semaphore和AQS的关系

image-20220212170612219

image-20220212170629266

CountDownLatch和ReentrantLock也是一样的。

三、AQS的认识

1、AQS的比喻

比喻:群面、单面

安排就坐、叫号、先来后到等HR的工作就是AQS做的工作

面试官不会去关心两个面试者是不是号码相同冲突了,也不想去管面试者需要一个地方坐着休息,这些都交给HR去做

比如:

Semaphore:一个人面试完了以后,后一个人才能进来继续面试

CountDownLatch:群面,等待10人到齐

Semaphore、CountDownLatch这些同步工具类,要做的就只是写下自己的“要人”规则。比如是“出一个,进一个”,或者说“凑齐10人,一起面试”。

剩下的招呼面试者的在那个活累活交给AQS来做

2、如果没有AQS

就需要每个协作工具自己实现:

  • 同步状态的原子性管理
  • 线程的阻塞与解除阻塞
  • 队列的管理

在并发的场景下,自己正确且高效实现这些内容,是相当有难度的,所以我们用AQS来帮我们把这些脏活累活都搞定,我们只关注业务逻辑就够了。

3、AQS的作用

AQS是一个用于构建锁、同步器、协作工具类的工具类(框架)。有了AQS以后,更多的协作工具类都可以很方便得被写出来

一句话总结:有了AQS,构建线程协作类就容易多了。

4、AQS的重要性、地位

AbstractQueuedSynchronizer是Doug Lea写的,从JDK1.5加入的一个基于FIFO等待队列实现的一个用于实现同步器的基础框架,我们用IDE看AQS的实现类,可以发现实现类有以下这些。

image-20220212172832120

四、AQS深入分析

1、AQS内部组成分析

AQS最核心的三大部分:

  • state:状态
  • 控制线程抢锁和配合的FIFO队列
  • 期望协作工具类去实现的获取/释放等重要方法

①state状态

image-20220212173809565

image-20220212173738975

在ReentrantLock中:

  • state用来表示“锁”的占有情况,包括可重入计数

  • 当state的值为0的时候,标识改Lock不被任何线程所占有

②控制线程抢锁和配合的FIFO队列

​ 这个队列用来存放“等待的线程”,AQS就是“排队管理器”,当多个线程争用同一把锁时,必须有排队机制将那些没能拿到锁的线程串在一起,当锁释放时,锁管理器就会挑选一个合适的线程来占有这个刚刚释放的锁。

​ AQS会维护一个等待的线程队列,把线程都放到这个队列里,这是一个双向链表的形式

image-20220212174221275

head是已经拿到锁的线程

③期望协作工具类去实现的方法

这里的获取和释放方法,是利用AQS的协作工具类里最重要的方法,是由协作类自己去实现的,并且含义各不相同。

获取方法

依赖state变量,经常会阻塞(比如获取不到锁的时候)

在Semaphore中,获取就是acquire方法,作用是获取一个许可证

而在CountDownLatch里面,获取就是await方法,作用是“等待,直到倒数结束”

释放方法

释放操作不会阻塞

在Semaphore中,释放就是release方法,作用是释放一个许可证

CountDownLatch里面,获取就是countDown方法,作用是“倒数1个数”

需要重写tryAcquire和tryRelease等方法

比如CountDownLatch

image-20220212174651929

2、AQS应用实例、源码解析

①AQS用法

image-20220212174831924

②CountDownLatch源码分析

内部类Sync继承AQS

image-20220212174915224

构造方法

image-20220212175132029

image-20220212175211848

getCount()方法

image-20220212175251633

image-20220212175413432

await()方法

进行等待,直到倒数结束

image-20220212175608698

调用的是AQS的方法

image-20220212175642324

重要的是下面的if,如果tryAcquireShared(aag) < 0那么就入队。如果不小于0,就不需要等待了。

在CountDownLatch里面,实现了tryAcquireShared这个方法

image-20220212175829727

如果剩余倒数的为0了,说明可以放行了,返回1,直接放行。如果state不等于0,返回-1,就会调用doAcquireSharedInterruptibly()方法让线程阻塞,把线程放到阻塞队列中。

countDown()方法

image-20220212180241764

releaseSharea()是AQS实现的

image-20220212180325537

这里调用的tryReleaseSharead()方法,是CountDownLatch自己实现的

image-20220212180359437

用for循环做CAS的自旋操作,如果为0了,就不用释放。一旦这个方法返回true就会调用doReleaseShared()方法

image-20220212180613021

总结

image-20220212180653727

③Semaphore中的AQS

image-20220212190303586

五、利用AQS实现一个自己的Latch门闩

一次释放,所有人都出发,一次性门闩。

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

/**
 * 描述:自己用AQS实现一个简单的线程协作器
 */
public class OneShotLatch {

    private final Sync sync = new Sync();

    public void signal() {
        sync.releaseShared(0);
    }

    public void await() {
        sync.acquireShared(0);
    }
    private class Sync extends AbstractQueuedSynchronizer {

        @Override
        protected int tryAcquireShared(int arg) {
            return (getState() == 1) ? 1 : -1;
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
            setState(1);
            return true;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        OneShotLatch oneShotLatch = new OneShotLatch();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "尝试获取latch,获取失败那就等待");
                    oneShotLatch.await();
                    System.out.println("开闸放行" +Thread.currentThread().getName()+ "继续运行");
                }
            }).start();
            Thread.sleep(5000);
            oneShotLatch.signal();
        }
    }
}
posted @ 2022-02-12 20:11  DarkerG  阅读(33)  评论(0编辑  收藏  举报