2022-09-25并发编程系列00
请注意,本文编写于 578 天前,最后修改于 578 天前,其中某些信息可能已经过时。

AQS

参考文档

AQS简介

AbstractQueuedSynchronizer

  • AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLockSemaphore,其他的诸如ReentrantReadWriteLockSynchronousQueueFutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。

AQS 核心思想

AQS核心思想是:

  • 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。
  • 如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中

CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。

AQS原理

基本概述

AQS 全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架

AQS特点

state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁 ;

  • getState - 获取 state 状态
  • setState - 设置 state 状态
  • compareAndSetState - cas 机制设置 state 状态

独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源

提供了基于 FIFO 的等待队列,类似于 MonitorEntryList

条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet

子类主要实现这样一些方法 :

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively

默认抛出 UnsupportedOperationException

AQS自定义锁

自定义锁

独占锁

独占锁是不可重入锁 , 已经获得锁的话, 再次请求获得锁会导致死锁.

  /**
     * 独占锁  同步器类
     */
    class MySync extends AbstractQueuedSynchronizer {

        @Override
        protected boolean tryAcquire(int arg) {
            // 尝试获取锁, 通过状态字段state设置锁
            if (compareAndSetState(0, 1)) {
                // 加锁, 并设置 owner 为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null);
            // 释放的时候, 因为已经获取独占锁了, 只有当前线程有锁, 所以直接设置状态即可
            // state 是volatile修饰, 在此之前对类属性的修改, 都会同步到主存, 并且
            setState(0);
            return true;
        }

        /**
         * 是否持有独占锁
         *
         * @return
         */
        @Override
        protected boolean isHeldExclusively() {
            return this.getState() == 1;
        }

        public Condition newCondition() {
            return new ConditionObject();
        }
    }

我们通过集成 AbstractQueuedSynchronizer 类来实现独占锁, AbstractQueuedSynchronizer 是使用 state 字段用于控制获取锁

package cn.knightzz.juc.aqs.custom;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @author 王天赐
 * @title: MyAqsLock
 * @projectName hm-juc-codes
 * @description: 自定义不可重入锁
 * @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
 * @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
 * @create: 2022-09-05 14:27
 */
@SuppressWarnings("all")
public class MyAqsLock implements Lock {

    /**
     * 独占锁  同步器类
     */
    class MySync extends AbstractQueuedSynchronizer {

        @Override
        protected boolean tryAcquire(int arg) {
            // 尝试获取锁, 通过状态字段state设置锁
            if (compareAndSetState(0, 1)) {
                // 加锁, 并设置 owner 为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null);
            // 释放的时候, 因为已经获取独占锁了, 只有当前线程有锁, 所以直接设置状态即可
            // state 是volatile修饰, 在此之前对类属性的修改, 都会同步到主存, 并且
            setState(0);
            return true;
        }

        /**
         * 是否持有独占锁
         *
         * @return
         */
        @Override
        protected boolean isHeldExclusively() {
            return this.getState() == 1;
        }

        public Condition newCondition() {
            return new ConditionObject();
        }
    }

    private MySync sync = new MySync();

    /**
     * 加锁, 加锁不成功会进入等待队列
     */
    @Override
    public void lock() {
        sync.acquire(1);
    }

    /**
     * 加锁, 但是可以打断
     *
     * @throws InterruptedException
     */
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.tryAcquire(1);
    }

    /**
     * 尝试加锁 (尝试一次)
     *
     * @return
     */
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    /**
     * 尝试加锁, 带超时
     *
     * @param time the maximum time to wait for the lock
     * @param unit the time unit of the {@code time} argument
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}

简单介绍下 :

  • 我们需要先自定义MySync, 继承AbstractQueuedSynchronizer类, 这个类实现了一些锁相关的方法
  • AbstractQueuedSynchronizer 通过使用一个volatile修饰的state字段+CAS来实现加锁/解锁

测试代码

package cn.knightzz.juc.aqs.custom;

import lombok.extern.slf4j.Slf4j;

import static java.lang.Thread.sleep;

/**
 * @author 王天赐
 * @title: MyAqsLockTest
 * @projectName hm-juc-codes
 * @description: 测试自定义锁
 * @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
 * @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
 * @create: 2022-09-06 21:47
 */
@SuppressWarnings("all")
@Slf4j(topic = "c.MyAqsLockTest")
public class MyAqsLockTest {

    public static void main(String[] args) {
        MyAqsLock lock = new MyAqsLock();
        new Thread(() -> {
            lock.lock();
            try {
                log.debug("locking...");
                sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                log.debug("unlocking...");
                lock.unlock();
            }
        },"t1").start();

        new Thread(() -> {
            lock.lock();
            try {
                log.debug("locking...");
            } finally {
                log.debug("unlocking...");
                lock.unlock();
            }
        },"t2").start();

    }
}

本文作者:王天赐

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!