Java基础-抽象队列同步器:AbstractQueuedSynchronizer(1)

简介: AQS定义了一套多线程访问共享资源的同步器框架。许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列。多线程争用资源被阻塞时会进入此队列。AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

简介

AQS定义了一套多线程访问共享资源的同步器框架。
许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。
它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列。
多线程争用资源被阻塞时会进入此队列。
AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

自定义同步器

不同的自定义同步器争用共享资源的方式也不同。
自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可;
至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。
自定义同步器实现时主要实现以下几种方法:

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

ReentrantLock

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

CountDownLatch

任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。
这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。
等到所有子线程都执行完后(即state=0),会unpark()主调用线程。
然后主调用线程就会从await()函数返回,继续后余动作。

Semaphore

是用来控制同时访问特定资源的线程数量,通过协调各个线程以保证合理地使用公共资源。
Semaphore通过使用计数器来控制对共享资源的访问。
如果计数器大于0,则允许访问。 如果为0,则拒绝访问。
计数器所计数的是允许访问共享资源的许可。 因此,要访问资源,必须从信号量中授予线程许可。
信号量控制登录用户,示例代码:

// 登录用户信号量
static final int MAX_AVAILABLE = 10;
static final Semaphore LOGIN_SEMAPHORE = new Semaphore(MAX_AVAILABLE, false);

// 线程池初始化
static ExecutorService executorService = Executors.newFixedThreadPool(10);

public static void main(String[] args) {
    // 连续登录十个用户
    for(int i = 0; i < 10; i++) {
        final int num = i;
        executorService.execute(() -> {
            if(LOGIN_SEMAPHORE.tryAcquire()) {
                System.out.println("用户:" + num + " 登录成功");
            } else {
                System.out.println("用户:" + num + " 登录失败");
            }
        });
    }
    executorService.shutdown();
    
    // 第十一个用户登录
    if(LOGIN_SEMAPHORE.tryAcquire()) {
        System.out.println("登录成功");
    } else {
        System.out.println("登录用户已满,登录失败");
    }
    
    // 有用户退出
    LOGIN_SEMAPHORE.release();
    
    // 第十二个用户登录
    if(LOGIN_SEMAPHORE.tryAcquire()) {
        System.out.println("登录成功");
    } else {
        System.out.println("登录用户已满,登录失败");
    }
}
相关文章
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
704 1
|
存储 监控 Java
JAVA线程池有哪些队列? 以及它们的适用场景案例
不同的线程池队列有着各自的特点和适用场景,在实际使用线程池时,需要根据具体的业务需求、系统资源状况以及对任务执行顺序、响应时间等方面的要求,合理选择相应的队列来构建线程池,以实现高效的任务处理。
1036 12
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
560 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
存储 算法 Java
【用Java学习数据结构系列】用堆实现优先级队列
【用Java学习数据结构系列】用堆实现优先级队列
369 0
|
存储 安全 Java
【用Java学习数据结构系列】探索栈和队列的无尽秘密
【用Java学习数据结构系列】探索栈和队列的无尽秘密
234 2
|
Java API 容器
JAVA并发编程系列(10)Condition条件队列-并发协作者
本文通过一线大厂面试真题,模拟消费者-生产者的场景,通过简洁的代码演示,帮助读者快速理解并复用。文章还详细解释了Condition与Object.wait()、notify()的区别,并探讨了Condition的核心原理及其实现机制。
|
9月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
427 1
|
9月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
394 1
|
10月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
434 0