AQS框架应用(五)

AQS框架应用(五)

定义:

​ 基于AQS框架,Doug Lea给出了很多内置的实现类

基于AQS框架的实现

实现类:

可以看出,有多个工具类是基于AQS框架实现的

  • 独占式同步信号量

    • ReentrantLock

    • Semaphore

  • 共享式同步信号量

    • CountDownLatch
  • 混合同步信号量

    • ReentrantReadWriteLock

ReentrantLock

定义:

​ 它是基于AQS实现的一个==独占式锁==,分为公平和非公平版本

AQS要求实现子类必须实现tryAcquire()/tryRelease()方法,接下来介绍ReentrantLock对于这两个方法的实现

组成:

  • 公平锁 —— FairSync
  • 非公平锁 —— NonFairSync

获取锁

NonFairSync.tryAcquire(int)

定义:

​ 在非公平情况下,去实现tryAcquire()方法,调用本方法去尝试获取锁

源码:

protected final boolean tryAcquire(int acquires) {
    // 调用nonfairTryAcquire()获取锁
    return nonfairTryAcquire(acquires);
}
nonfairTryAcquire(int)

定义:

​ 调用本方法去尝试获取锁,套娃

源码:

/**
 * 尝试获取非公平锁
 * acquires == 1 默认
 */
final boolean nonfairTryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 拿到当前同步信号量的值
    int c = getState();
    // 判断state状态是否为0,为0直接加锁
    if (c == 0) {
		// 不需要判断同步队列(CLH)中是否有排队等待线程,因为是非公平锁
        // 将state修改为1,标记获得锁
        if (compareAndSetState(0, acquires)) {
            // 独占状态锁持有者指向当前线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    /**
     * state状态不为0,判断锁持有者是否是当前线程,
     * 如果是当前线程持有 则state+1
     */
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    //加锁失败
    return false;
}

总结:

  1. 获取state信号量,如果为0,不需要判断CLH是否有等待线程,直接CAS抢锁,return抢锁是否成功
  2. 如果不为0,判断是不是自己拿到了锁
  3. 如果是自己拿到锁,state累加return true
  4. 如果不是自己拿到锁,则return false
FairSync.tryAcquire(int)

定义:

​ 在公平情况下,去实现tryAcquire()方法,调用本方法去尝试获取锁

源码:

/**
 * 重写aqs中的方法逻辑
 * 尝试加锁,被AQS的acquire()方法调用
 */
protected final boolean tryAcquire(int acquires) {
     // 获取当前线程
    final Thread current = Thread.currentThread();
    // 拿到当前同步信号量的值
    int c = getState();
    // 判断state状态是否为0,为0直接加锁
    if (c == 0) {
        /**
         * ==与非公平锁中的区别==
    	 * 需要先判断 CLH队列 当中是否有等待的节点
     	 * 如果没有则可以尝试CAS获取锁
     	 */
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            // 独占线程指向当前线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 如果status不为0,则判断持有锁的线程 是否 是当前线程
    else if (current == getExclusiveOwnerThread()) {
        // 是当前线程,信号量++
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // 加锁失败
    return false;
}

总结:

  1. 拿到state信号量,如果为0
    1. 判断CLH队列是否有等待线程,有等待线程return false
    2. 如果CLH中没有等待线程,进行CAS抢锁,return抢锁是否成功
  2. 如果不为0
    1. 判断当前持有锁线程是否为自己不是return false
    2. 是自己,则让信号量state = state + acquiresreturn true

释放锁

定义:

​ 公平锁与非公平锁释放锁的逻辑是一样的,都是调用同一个方法tryRelease()

tryRelease()

定义:

​ 该方法用于释放锁

源码:

/**
 * 释放锁
 */
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;
}

总结:

  1. 计算释放后的信号量
  2. 合法性判断 【是否为拥有锁的线程来释放信号量
  3. state==0 表示完全归还信号量 【也就是释放锁】,return true
  4. 否则修改state,return false 【释放部分信号量成功,但未释放锁】

应用 BlockingQueue

定义:

​ BlockingQueue,是用于解决 并发生产者 - 消费者问题 的最有用的类。它保证任意时刻只有一个线程可以进行take或者put操作,并且提供了超时 return null 的机制。

类型:

  1. 无限队列 —— 几乎可以无限增长
  2. 有限队列 —— 定义了最大容量

队列数据结构:

  • 通常用链表或者数组实现
  • 具有FIFO先进先出特性,也有双向队列(Deque)优先级队列
  • 主要操作 —— 入队(EnQueue) 与 出队 (DeQueue)

常见的4种阻塞队列

  • ArrayBlockingQueue —— 由数组支持的有界队列
  • LinkedBlockingQueue —— 由链接节点支持的可选有界队列
  • PriorityBlockingQueue —— 由优先级堆支持的无界优先级队列
  • DelayQueue —— 由优先级堆支持的、基于时间的调度队列
原理

阻塞队列每一种操作都根据ReentrantLock来加锁进行保证 任意时刻==只有一个线程可以进行take或者put操作==

以ArrayBlockingQueue来举例:

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    
    /** 构造函数中,单例生成 */
    final ReentrantLock lock;
    
    // 构造函数,单例生成一个
    public ArrayBlockingQueue(int capacity, boolean fair) {
        // ...
        lock = new ReentrantLock(fair);
        // ...
    }

    // 添加元素操作
    public boolean offer(E e) {
        checkNotNull(e);
		// 获取单例锁
        final ReentrantLock lock = this.lock;
        // 通过lock锁,锁住
        lock.lock();
        try {
            if (count == items.length)
                return false;
            else {
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }
    
    // 删除元素操作
    public E poll() {
        // 获取单例锁
        final ReentrantLock lock = this.lock;
        // 通过lock锁,锁住
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }
}
ArrayBlockingQueue

定义:

​ 队列基于数组实现,容量大小在创建时已经定义

BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>();

ArrayBlockingQueue

应用场景:

在线程池中有比较多的应用,生产者 - 消费者场景

工作原理:

基于ReentrantLock保证线程安全,根据Condition实现队列满时的阻塞

LinkedBlockingQueue

定义:

​ 基于链表实现的无界队列(理论上有界)

BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();

特点:

向无限队列添加元素的所有操作都将永远不会阻塞,[注意这里 不是说不会加锁 保证线程安全],因此它可以增长到非常大的容量。

应用场景:

生产者 - 消费者场景使用时 消费者应该能够像生产者向队列添加消息一样快地消费消息 【否则产生OutOfMemory异常】

DelayQueue

定义:

​ 由优先级堆支持的、基于时间的调度队列,

  • 内部基于无界队列PriorityQueue实现
  • 无界队列基于数组的扩容实现
BlockingQueue<String> blockingQueue = new DelayQueue();

要求:

​ 入队的对象必须要实现Delayed接口,而Delayed集成自Comparable接口

应用场景:

​ 电影票

工作原理:

​ 队列内部会根据时间优先级进行排序。延迟类线程池周期执行。

BlockingQueue API

BlockingQueue 接口的所有方法可以分为两大类:

  • 向队列添加元素的方法
  • 检索这些元素的方法

队列满/空的情况下,来自这两个组的每个方法的行为都不同

添加元素

方法说明
add()如果插入成功则返回 true,否则抛出 IllegalStateException 异常
put()将指定的元素插入队列,如果队列满了,那么会阻塞直到有空间插入
offer()如果插入成功则返回 true,否则返回 false
offer(E e, long timeout, TimeUnit unit)尝试将元素插入队列,如果队列已满,那么会阻塞直到有空间插入

检索元素

方法说明
take()获取队列的头部元素并将其删除,如果队列为空,则阻塞并等待元素变为可用
poll(long timeout, TimeUnit unit)检索并删除队列的头部,如有必要,等待指定的等待时间以使元素可用,如果超时,则返回 null

构建生产者 - 消费者程序时,这些方法是 BlockingQueue 接口中最重要的构建块。

Semaphore

定义:

​ 它是基于AQS实现的一个==共享式==锁,分为公平和非公平版本

AQS要求实现子类必须实现tryAcquire()/tryRelease()方法,接下来介绍ReentrantLock对于这两个方法的实现

获取资源

非公平的acquire()

定义:

非公平情况下,获取共享式资源

源码:

// AQS父类顶层入口
public final void acquireShared(int arg) {
    // 获取锁失败,则节点阻塞
    if (tryAcquireShared(arg) < 0)
        // AQS维护的节点阻塞方法
        doAcquireShared(arg);
}

// Semaphore类实现的tryAcquire()允许外部调用的方法 【非公平】
public boolean tryAcquire() {
	// 根据下面nonfairTryAcquireShared()
    // 返回正值,说明抢到了锁
    // 返回负值,说明没有抢到锁 【资源不足】
    return sync.nonfairTryAcquireShared(1) >= 0;
}

protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}
/* 获取非公平共享信号量 锁 */
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        // 拿到当前的信号量 【剩余资源】
        int available = getState();
        // 尝试分配后的信号量
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            // remaining<0 进入这个return,说明资源不足,返回负值
            // CAS 进入这个return,说明分配成功,返回正值
            return remaining;
    }
}
公平的acquire()

定义:

公平情况下,获取共享式锁

源码:

// AQS父类顶层入口
public final void acquireShared(int arg) {
    // 获取锁失败,则节点阻塞
    if (tryAcquireShared(arg) < 0)
        // AQS维护的节点阻塞方法
        doAcquireShared(arg);
}
// Semaphore子类根据AQS要求实现的tryAcquire()
protected int tryAcquireShared(int acquires) {
    for (;;) {
        // 判断CLH等待队列是否为空 【与非公平锁的不同】
        if (hasQueuedPredecessors())
            return -1;
        // 同样的逻辑
        int available = getState();
        // 剩余量
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            // remaining<0 进入这个return,说明资源不足,返回负值
            // CAS 进入这个return,说明分配成功,返回正值
            return remaining;
    }
}
总结
  1. 拿到当前信号量,计算剩余量remaining
  2. 如果剩余量小于零,返回负值
  3. 如果剩余量大于零
    • 非公平锁进行CAS抢锁
    • 公平锁判断队列有无节点
      • 有则入队 返回负值
      • 没有则进行CAS抢锁

注意:

  • 默认情况下,是非公平锁。

  • 直接调用 tryAcquire(),走非公平锁的逻辑

  • 调用公平还是非公平,取决于实例化的Sync

释放资源

定义:

​ 公平与非公平一样,释放共享式资源

release()

归还资源

源码:

public void release() {
    sync.releaseShared(1);
}

// 实现AQS框架指定的tryReleaseShared()方法
protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        // 获取当前信号量
        int current = getState();
        // 做求和得到 新的信号量
        int next = current + releases;
        
        if (next < current) // 如果溢出
            throw new Error("Maximum permit count exceeded");
        // CAS不断设置
        if (compareAndSetState(current, next))
            return true;
    }
}

总结

Semaphore是实现了AQS的一个共享式同步信号量的类,它可以用作服务限流设置

  • accquire()方法对共享同步信号量作减法 【取出操作
  • release()方法对共享同步信号量作加法 【归还操作

CountDownLatch

定义:

​ 它是基于AQS实现的、可中断的一种多线程计数器

开始计数 countDown()

​ 类的入口方法为 countDown()方法

public void countDown() {
    sync.releaseShared(1);
}

protected boolean tryReleaseShared(int releases) {
    for (;;) {
        // 获取信号量,资源
        int c = getState();
        // 如果资源为0,则没有资源可给了,返回false
        if (c == 0)
            return false;
        // 剩余资源
        int nextc = c-1;
        // CAS释放
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

等待总数 await()

​ 类的入口方法为 await()方法

// 方法入口
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

// AQS中,如果中断则终止,尝试获取同步信号量
public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        // 小于0表示失败,失败则进入等待队列
        doAcquireSharedInterruptibly(arg);
}

// CountDownLatch根据AQS规定的实现tryAcquireShared()
protected int tryAcquireShared(int acquires) {
    // 判断当前信号量是否为0
    return (getState() == 0) ? 1 : -1;
}

总结

CountDownLatch是一个对多线程计数的类,实现了AQS中共享式同步信号量

  • countDown():对信号量–,可用于统计线程数
  • await():阻塞,对信号量进行判断,如果信号量state == 0,说明不需要再等待任何线程,可以往下执行。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值