您当前的位置: 首页 >  Java
  • 0浏览

    0关注

    674博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

java并发AQS原理

沙漠一只雕得儿得儿 发布时间:2021-12-03 13:50:30 ,浏览量:0

  • 1.前言
  • 2.ReentrantLock介绍
  • 3.AQS
    • 3.1主要原理
    • 3.2数据结构
    • 3.3不同模式加锁过程
    • 3.4 自定义同步器中需要实现的重要方法
  • 4 ReentrantLock中的AQS分析
    • 4.1加锁过程
      • 4.1.1第一步:尝试获取锁(利用cas更改state值)
      • 4.1.2第二步:线程加入队列
      • 4.1.3第三步:队列中的节点自旋并阻塞
    • 4.2解锁过程
      • 4.2.1 第一步:资源释放
      • 4.2.2 第二步,如果资源全部释放则唤醒阻塞的线程
    • 4.3 Condition 实现原理
      • 4.3.1 condition队列数据结构
      • 4.3.2 await方法与signal方法执行逻辑
  • 参考文章
1.前言

AQS位于java.util.concurrent.locks包下,全名 AbstractQueuedSynchronizer,本质上是一套同步器框架,java中许多线程同步工具都是基于它的。

工具

是什么

ReentrantLock
ReentrantLock是可重入的排它锁,同一时刻仅有一个线程可以进行访问

ReentrantReadWriteLock 

ReentrantReadWriteLock维护着一对锁,一个读锁,一个写锁,同一个时间,允许多个线程同时获取读锁

CountDownLatch

使用给定计数初始化CountDownLatch,由于调用countDown方法,在当前计数到达0之前,await方法会一直阻塞,
之后会释放等待线程计数无法被重置

Semaphore

信号量Semaphore是一个控制访问共享资源的计数器,信号量初始化许可的个数即是当前允许线程同时访问总的个数。

在获取许可前,会阻塞acquire的线程。每一个release会添加一个许可,并可以释放一个正在阻塞的获取者。

本篇将以使用最多的ReentrantLock为例,进行AQS原理分析

在正式进入介绍之前先理解一下几个常见的锁概念:

公平锁与非公平锁

公平锁:公平锁中,锁的获取顺序是按照先到先得的原则进行的。实现上,某个线程竞争锁时,只要锁的等待队列不为空,则该线程需要阻塞插入队列尾部,获取竞争锁的机会

非公平锁:后到的新线程可以直接竞争锁,只有在竞争锁失败时,才会被阻塞插入队列尾部。

共享锁与排他锁

共享锁与排他锁最主要的区别就是:排他锁在同一时刻只能被同一个线程占有,而共享锁可被多个线程持有。

可重入锁

可重入锁支持被同一个线程多次获取,即当一个线程获取锁之后,这个线程还可以再次获取锁

我们通过AQS都可以实现上述的公平锁,非公平锁,共享锁,排他锁,可重入锁,那么怎么实现呢,稍后一一介绍

2.ReentrantLock介绍
ReentrantLock是一种可重入排他锁

类图:

如上图,ReentrantLock基于Sync内部类实现了Lock接口,其中Sync实现了AQS,提供了NonfairSync和FairSync 2种实现。

ReentrantLock与Synchronized区别

特征

ReentrantLock

Synchronized

功能支持响应中断,超时,尝试获取不支持锁类型公平锁,非公平锁非公平锁实现AQSjvm监视器重入性可重入可重入解锁手动解锁自动解锁

非公平锁的加锁流程

final void lock() {

    if (compareAndSetState(01))

        setExclusiveOwnerThread(Thread.currentThread());

    else

        acquire(1);

}

通过CAS设置status

  • 如果成功的话,说明获取锁成功,并将独占线程设为当前线程
  • 失败的话,说明获取锁失败,调用acquire(1)

公平锁的加锁流程

final void lock() {

    acquire(1);

}

公平锁中,直接调用acquire(1)方法

公平锁与非公平锁的解锁流程是一样的

public void unlock() {

    sync.release(1);

}

由上可以看出,在加锁流程中,不论是公平锁还是非公平锁,都调用了acquire()方法以及解锁流程中调用的release方法,正是在AQS中定义的核心方法

3.AQS 3.1主要原理

AQS主要数据结构为双向链表+state

主要原理如下:

线程1请求获取资源state空闲,将线程1设置为工作线程,并锁定资源。而其他线程请求获取资源时发现资源被占用,AQS就将请求资源的线程阻塞并封装成节点入FIFO队列(尾部入队列,头部出队列),当线程1执行完毕,完全释放所有资源后,AQS会唤醒线程2,使得线程2获得竞争资源的机会,以此往复。。。

3.2数据结构

这里的数据结构,是指队列中的节点

类型

属性

描述

volatile Node
prev前驱节点
volatile Node
next后继节点
volatile Thread
thread处于该节点的线程
volatile int
waitStatus

节点在队列中的状态

CANCELLED 1:表示由于线程超时或中断导致的线程获取锁的请求已经取消了

SIGNAL -1:线程准备好,等待资源释放

CONDITION -2:节点在condition队列中,节点线程等待唤醒

PROPAGATE -3:线程处于SHARED(共享模式)时,会使用

0:Node被初始化的默认值

  
Node
nextWaiter返回处于CONDITION状态的节点SHARED /EXCLUSIVE 

SHARED :共享模式等待锁

EXCLUSIVE:独占模式等待锁

volatile int
state临界资源

3.3不同模式加锁过程

3.4 自定义同步器中需要实现的重要方法

方法名称

描述

tryAcquire(int)
独占方式,尝试获取资源,成功返回true,失败返回false
tryRelease(int)
独占方式,尝试释放资源,成功返回true,失败返回false
tryAcquireShared(int)
共享方式,尝试获取资源
tryReleaseShared(int)
共享方式,尝试释放资源

其中tryAcquire-tryRelease是一对、tryAcquireShared-tryReleaseShared是一对,可以只实现其中的一对,如ReentrantLock是独占锁,所以实现了tryAcquire-tryRelease

4 ReentrantLock中的AQS分析 4.1加锁过程

以非公平锁为例进行代码分析

4.1.1第一步:尝试获取锁(利用cas更改state值)

如果成功则获取锁,失败则进行acquire(1)

static final class NonfairSync extends Sync {

    ...

    final void lock() {

        //直接尝试获取锁,

        if (compareAndSetState(01))

            setExclusiveOwnerThread(Thread.currentThread());

        else

            acquire(1);

    }

  ...

}

获取锁失败

public final void acquire(int arg) {

    if (!tryAcquire(arg) &&

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

        selfInterrupt();

}

非公平锁中tryAcquire()实现,如果返回true则获取锁,返回false,则需要加入队列

protected final boolean tryAcquire(int acquires) {

    return nonfairTryAcquire(acquires);

}

final boolean nonfairTryAcquire(int acquires) {

    final Thread current = Thread.currentThread();

    int c = getState();

    //c==0说明队列为空

    if (c == 0) {

        //尝试独占资源

        if (compareAndSetState(0, acquires)) {

            setExclusiveOwnerThread(current);

            return true;

        }

    }

    //可重入锁逻辑,判断独占资源的线程是否就是当前的线程

    else if (current == getExclusiveOwnerThread()) {

        int nextc = c + acquires;

        if (nextc 0说明是取消状态,循环向前查找,把取消的节点删除

    if (ws > 0) {

        /*

         * Predecessor was cancelled. Skip over predecessors and

         * indicate retry.

         */

        do {

            node.prev = pred = pred.prev;

        while (pred.waitStatus > 0);

        pred.next = node;

    else {

        /*

         * waitStatus must be 0 or PROPAGATE.  Indicate that we

         * need a signal, but don't park yet.  Caller will need to

         * retry to make sure it cannot acquire before parking.

         */

        //这时waitStatus 只剩下0或者PROPAGATE

        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

    }

    return false;

}

阻塞

private final boolean parkAndCheckInterrupt() {

    LockSupport.park(this);

    return Thread.interrupted();

}

4.2解锁过程

ReentrantLock在解锁的时候,并不区分公平锁和非公平锁

4.2.1 第一步:资源释放

public void unlock() {

    sync.release(1);

}

public final boolean release(int arg) {

    if (tryRelease(arg)) {

        Node h = head;

        if (h != null && h.waitStatus != 0)

            unparkSuccessor(h);

        return true;

    }

    return false;

}

//tryRelease中资源全部释放返回true,否则返回false

protected final boolean tryRelease(int releases) {

    //减少可重入次数

    int c = getState() - releases;

    if (Thread.currentThread() != getExclusiveOwnerThread())

        throw new IllegalMonitorStateException();

    boolean free = false;

    //如果资源占用全部释放,则将独占线程释放

    if (c == 0) {

        free = true;

        setExclusiveOwnerThread(null);

    }

    setState(c);

    return free;

}

4.2.2 第二步,如果资源全部释放则唤醒阻塞的线程

public final boolean release(int arg) {

    if (tryRelease(arg)) {

        Node h = head;

        //

        if (h != null && h.waitStatus != 0)

            

            unparkSuccessor(h);

        return true;

    }

    return false;

}

private void unparkSuccessor(Node node) {

    /*

     * If status is negative (i.e., possibly needing signal) try

     * to clear in anticipation of signalling.  It is OK if this

     * fails or if status is changed by waiting thread.

     */

    int ws = node.waitStatus;

    if (ws  0) {

        s = null;

        for (Node t = tail; t != null && t != node; t = t.prev)

            if (t.waitStatus  0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))

        LockSupport.unpark(node.thread);

    return true;

}

待续。。。
参考文章

https://segmentfault.com/a/1190000016462281

从ReentrantLock的实现看AQS的原理及应用 - 美团技术团队

关注
打赏
1657159701
查看更多评论
立即登录/注册

微信扫码登录

0.0489s