工作中需要用到的Java知识(AQS篇)

AQS是在面试的时候比较常问的内容,那么今天我们就来简单了解一下什么是AQS。

什么是AQS?

我们来简单说说什么是AQS,AQS其实是指Java中的AbstractQueuedSynchronizer类,这个类在java.util.concurrent.locks包下,是Java用来实现轻量级锁的类。

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

CLH队列是根据发明者三人名字的首字母命名的。CLH队列从逻辑上形成一个锁等待队列(实际无队列实例,仅通过节点间的关联来实现)从而实现加锁,CLH锁只支持按顺序加锁和解锁(FIFO先入先出),不支持重入,不支持中断

AQS中还有一个state变量,用来判断当前是否为同步状态(通过CAS方法来改变state的值),0:资源空闲,当state变量大于0时表示资源正在锁定中,且state的值表示锁定资源的重入次数。

AQS的工作流程就是基于CLH虚拟队列,用volatile关键字修饰共享变量state,想要获取锁的线程通过CAS方法去改变state变量,成功则获取到锁,失败则进入等待队列中等待唤醒

AQS采用了模板模式的设计方法

我们只需要按照模板创建自己的类,就可以生成一个自定义的同步器(锁)。

首先我们要遵守这两步:

1. 内部类同步器需要继承AbstractQueuedSynchronizer并重写指定的方法。

2. 外部类同步器需要实现Lock接口与Serializable接口提供对外服务。

我们去创建自定义同步器的时候只需要实现共享变量state的获取与释放即可,等待队列的维护都由AQS底层实现。

自定义同步器需要实现的方法:

方法名作用
isHeldExclusively()该线程是否正在独占资源。只有用到condition才需要去实现它
tryAcquire(int)独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int)共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int)共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

ReentrantLock为例,(可重入独占式锁):state变量初始化为0,表示未锁定状态,A线程lock()时,会调用tryAcquire()独占锁并将state+1.之后其他线程再想tryAcquire的时候就会失败,直到A线程unlock()到state=0为止,其他线程才有机会获取该锁。A释放锁之前可以重复获取此锁(state累加),这就是可重入的概念。
注意:获取多少次锁就要释放多少次锁,保证state是能回到零态的。

CountDownLatch为例,任务分N个子线程去执行,state就初始化为N,N个线程并行执行,每个线程执行完之后countDown()一次,state就会CAS减一。当N子线程全部执行完毕,state=0,会unpark()主调用线程,主调用线程就会从await()函数返回,继续之后的动作。

接下来我们尝试自定义实现一个同步器。

/**
 * @description: 自定义同步器
 * @author: Me
 * @createDate: 2022/11/21 11:09
 * @version: 1.0
 */
public class NonReentrantLock implements Lock, Serializable {
    // 同步器实现时一般都将自定义同步器定义为内部类,供自己使用,并实现某个接口,向外提供服务,一般实现Lock接口
    // 内部类的好处在于可以变相实现多继承
    // 内部类会对同一个包下的其他类隐藏,内部类可以使用private关键字修饰
    // 内部类可以访问外部类的全部字段与方法,外部类访问内部类需要创建内部类对象,内部类中不能有静态成员
    private static class Sync extends AbstractQueuedSynchronizer{
        @Override
        protected boolean tryAcquire(int arg) {
            assert arg == 1;
            // cas方法,将变量0改为1
            if (compareAndSetState(0,1)){
                // 修改成功则上锁
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            assert arg == 1;
            //如果state为0,则抛出异常
            if (getState()==0){
                throw new IllegalMonitorStateException();
            }
            // 如果state变量不为0,则释放锁
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            // 是否锁已经被持有
            return getState() == 1;
        }

        //提供条件变量接口
        public Condition newCondition(){
            return new ConditionObject();
        }
    }

    Sync sync = new Sync();

    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @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();
    }
}

到这里AQS就简单介绍完啦,希望对小伙伴们有所帮助。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值