1.aqsԴ?码分????
2.AbstractQueuedSynchronizer详解
3.33张图解析ReentrantReadWriteLock源码
4.硬核干货:4W字从源码上分析JUC线程池ThreadPoolExecutor的实现原理
5.后端面经-JavaAQS详解
6.Java并发系列 | Semaphore源码分析
aqsԴ?????
我最近建立了一个在线自习室(App:番茄ToDO)用于相互监督学习,感兴趣的码分小伙伴可以加入。自习室加入码:D5A7A
Java并发包下的码分类大多基于AQS(AbstractQueuedSynchronizer)框架实现,而AQS线程安全的码分实现依赖于两个关键类:Unsafe和LockSupport。
其中,码分Unsafe主要提供CAS操作(关于CAS,码分什么是源码框架在文章《读懂AtomicInteger源码(多线程专题)》中讲解过),码分LockSupport主要提供park/unpark操作。码分实际上,码分park/unpark操作的码分最终调用还是基于Unsafe类,因此Unsafe类才是码分核心。
Unsafe类的码分实现是由native关键字说明的,这意味着这个方法是码分原生函数,是码分用C/C++语言实现的,并被编译成了DLL,码分由Java去调用。
park函数的作用是将当前调用线程阻塞,而unpark函数则是唤醒指定线程。
park是等待一个许可,unpark是为某线程提供一个许可。如果线程A调用park,除非另一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。每次调用一次park,需要有一个unpark来解锁。
并且,unpark可以先于park调用,但不管unpark先调用多少次,都只提供一个许可,不可叠加。只需要一次park来消费掉unpark带来的许可,再次调用会阻塞。
在Linux系统下,park和unpark是通过Posix线程库pthread中的mutex(互斥量)和condition(条件变量)来实现的。
简单来说,mutex和condition保护了一个叫_counter的信号量。当park时,这个变量被设置为0,添添呼源码当unpark时,这个变量被设置为1。当_counter=0时线程阻塞,当_counter>0时直接设为0并返回。
每个Java线程都有一个Parker实例,Parker类的部分源码如下:
由源码可知,Parker类继承于PlatformParker,实际上是用Posix的mutex和condition来实现的。Parker类里的_counter字段,就是用来记录park和unpark是否需要阻塞的标识。
具体的执行逻辑已经用注释标记在代码中,简要来说,就是检查_counter是不是大于0,如果是,则把_counter设置为0,返回。如果等于零,继续执行,阻塞等待。
unpark直接设置_counter为1,再unlock mutex返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程。源码如下:
(如果不会下载JVM源码可以后台回复“jdk”,获得下载压缩包)
AbstractQueuedSynchronizer详解
AbstractQueuedSynchronizer,简称AQS,究竟是什么呢?
它提供了一个框架,用于实现基于先进先出(FIFO)等待队列的阻塞锁及相关同步器(如信号量、事件等)。这个类的设计目的是为了成为那些依赖单一原子整数值来表示状态的同步器的有效基础。
从JDK的注释来看,AQS是一个实现阻塞锁和相关同步器的框架,它基于先进先出的等待队列。这个类适用于大多数使用整数来表示状态的同步器。这里提到的框架实际上是一个类。
那么,在什么情况下我们会使用AQS呢?
在编程中,兰蔻溯源码我们经常遇到并发问题。有了并发,就涉及资源共享;而资源共享又需要处理资源的同步访问。在处理同步时,我们需要解决竞争发生时的等待问题和竞争解除后的唤醒问题。AQS就是这样一个便于实现这种同步机制的框架。我们日常使用的ReentrantLock、ReentrantReadWriteLock以及ArrayBlockingQueue等都是基于AQS实现的。
如果没有AQS,又会如何呢?
在没有AQS的情况下,要实现锁需要怎么做呢?这些问题都是我们在实现锁时需要考虑的。
在AQS的情况下,情况又是怎样的呢?
AQS作为基础类,主要解决了在锁不可用时的等待,以及锁释放后的唤醒。锁状态的设定、如何获取锁以及如何释放锁,都是需要相应的同步机制自己实现的。因此,在使用AQS时,需要实现以下方法:
类似于ReentrantLock,使用state来标识锁的状态,state = 0表示锁未被获取,当state > 0表示锁已被获取。此外,实现了AQS的tryAcquire()和tryRelease()来处理state的状态,以处理锁的状态。这样就可以实现基础的ReentrantLock。因此,在AQS的支持下,实现类似的阻塞锁非常方便。
以上我们介绍了AQS是什么,以及AQS的具体使用场景,下面我们详细介绍AQS的具体实现机制。
我们先简单看一下AQS的核心流程。
接下来,我们将结合ReentrantLock来详细查看基于AQS如何实现排他锁。百码云源码
首先,我们来看一下ReentrantLock是如何使用int类型的state来表示锁的状态的。state = 0表示锁未被获取。state > 0表示锁已被持有。由于是可重入锁,state表示了这个锁被同一个线程获取了多少次。
上面的代码片段是ReentrantLock的核心代码,为了便于解释,省略了其他代码。首先我们可以看到,在使用AQS时,通常不是直接实现AQS,而是创建一个内部的辅助类。
Subclasses should be defined as non-public internal helper classes that are used to implement the synchronization properties of their enclosing class
然后加锁和释放锁对应的lock和unlock,直接调用辅助类的lock和unlock。
下面我们来看一下Sync类的代码。
上面我们来看一下非公平锁NonfairSync的实现。
下面我们来看一下AQS中的acquire方法。
上面我们看了获取锁的方法acquire,下面看下释放锁的release。
总结一下,AQS通过tryAcquire以及tryRelease两个方法,来进行锁的获取以及释放,两个都是非阻塞的方法。如果获取成功则返回,如果获取失败,则添加队列。在队列中获取锁,获取失败则挂起,等待锁被释放后被重新唤醒。唤醒后还是会去尝试获取锁。释放锁的时候如果检查到已经全部释放,则会唤醒被挂起的线程。这样通过tryAcquire和tryRelease,实现了锁的获取以及等待,以及锁的释放。具体锁状态的物流出车源码控制,子类则是通过tryAcquire和tryRelease进行控制的。
在介绍ReentrantLock时,我们简化了很多代码。了解基本原理后,再去读源码会事半功倍。
张图解析ReentrantReadWriteLock源码
今天,我们深入探讨ReentrantReadWriteLock源码,解析其内部结构与工作原理。文章分为多个部分,逐一剖析读写锁的创建、获取与释放过程。读写锁规范与实现
ReentrantReadWriteLock(简称RRW)作为读写锁,其核心功能在于控制并发访问的读与写操作。为了规范读写锁的使用,RRW首先声明了ReadWriteLock接口,并通过ReadLock与WriteLock实现接口,确保读锁与写锁的正确操作。 为了实现锁的基本功能,WriteLock与ReadLock都继承了Lock接口。这些类内部依赖于AQS(AbstractQueuedSynchronizer)抽象类,AQS为加锁和解锁过程提供了统一的模板函数,简化了锁实现的复杂性。核心组件与流程
AQS提供了一套多线程访问共享资源的同步模板,包括tryAcquire、release等核心抽象函数。WriteLock与ReadLock通过继承Sync类,实现了AQS中的tryAcquire、release(写锁)和tryAcquireShared、tryReleaseShared(读锁)函数。 Sync类在ReentrantReadWriteLock中扮演关键角色,它不仅实现了AQS的抽象函数,还通过位运算优化了读写锁状态的存储,减少了资源消耗。此外,Sync类还定义了HoldCounter与ThreadLocalHoldCounter,进一步管理锁的状态与操作。公平与非公平策略
为了适应不同场景的需求,ReentrantReadWriteLock支持公平与非公平策略。通过Sync类的FairSync与NonfairSync子类,实现了读锁与写锁的阻塞控制。公平策略确保了线程按顺序获取锁,而非公平策略允许各线程独立竞争。全局图与细节解析
文章最后,构建了一张全局图,清晰展示了ReentrantReadWriteLock的各个组件及其相互关系。通过深入细节,分别解释了读写锁的创建、获取与释放过程。以Lock接口的lock与unlock方法为主线,追踪了从Sync类出发的实现路径,包括tryAcquire、tryRelease等核心函数,以及它们在流程图中的表现。 总结,ReentrantReadWriteLock通过继承AQS并扩展公平与非公平策略,实现了高效、灵活的读写锁功能。通过精心设计的Sync类及其相关组件,确保了多线程环境下的并发控制与资源访问优化。深入理解其内部实现,有助于在实际项目中更好地应用读写锁,提升并发性能与系统稳定性。硬核干货:4W字从源码上分析JUC线程池ThreadPoolExecutor的实现原理
深入剖析JUC线程池ThreadPoolExecutor的执行核心 早有计划详尽解读ThreadPoolExecutor的源码,因事务繁忙未能及时整理。在之前的文章中,我们曾提及Doug Lea设计的Executor接口,其顶层方法execute()是线程池扩展的基础。本文将重点关注ThreadPoolExecutor#execute()的实现,结合简化示例,逐步解析。 ThreadPoolExecutor的核心功能包括固定的核心线程、额外的非核心线程、任务队列和拒绝策略。它的设计巧妙地运用了JUC同步器框架AbstractQueuedSynchronizer(AQS),以及位操作和CAS技术。以核心线程为例,设计上允许它们在任务队列满时阻塞,或者在超时后轮询,而非核心线程则在必要时创建。 创建ThreadPoolExecutor时,我们需要指定核心线程数、最大线程数、任务队列类型等。当核心线程和任务队列满载时,会尝试添加额外线程处理新任务。线程池的状态控制至关重要,通过整型变量ctl进行管理和状态转换,如RUNNING、SHUTDOWN、STOP等,状态控制机制包括工作线程上限数量的位操作。 接下来,我们深入剖析execute()方法。首先,方法会检查线程池状态和工作线程数量,确保在需要时添加新线程。这里涉及一个疑惑:为何需要二次检查?这主要是为了处理任务队列变化和线程池状态切换。任务提交流程中,addWorker()方法负责创建工作线程,其内部逻辑复杂,包含线程中断和适配器Worker的创建。 Worker内部类是线程池核心,它继承自AQS,实现Runnable接口。Worker的构造和run()方法共同确保任务的执行,同时处理线程中断和生命周期的终结。getTask()方法是工作线程获取任务的关键,它会检查任务队列状态和线程池大小,确保资源的有效利用。 线程池关闭操作通过shutdown()、shutdownNow()和awaitTermination()方法实现,它们涉及线程中断、任务队列清理和状态更新等步骤,以确保线程池的有序退出。在这些方法中,可重入锁mainLock和条件变量termination起到了关键作用,保证了线程安全。 ThreadPoolExecutor还提供了钩子方法,允许开发者在特定时刻执行自定义操作。除此之外,它还包含了监控统计、任务队列操作等实用功能,每个功能的实现都是对execute()核心逻辑的扩展和优化。 总的来说,ThreadPoolExecutor的execute()方法是整个线程池的核心,它的实现原理复杂而精细。后续将陆续分析ExecutorService和ScheduledThreadPoolExecutor的源码,深入探讨线程池的扩展和调度机制。敬请关注,期待下文的详细解析。后端面经-JavaAQS详解
AQS是什么?
AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock。简单来说,AQS定义了一套框架,来实现同步类。
AQS的核心思想是对于共享资源,维护一个双端队列来管理线程,队列中的线程依次获取资源,获取不到的线程进入队列等待,直到资源释放,队列中的线程依次获取资源。AQS的基本框架如图所示:
资源state变量表示共享资源,通常是int类型。CLH双向队列是一种基于逻辑队列非线程饥饿的自旋公平锁,具体介绍可参考此篇博客。CLH中每个节点都表示一个线程,处于头部的节点获取资源,而其他资源则等待。Node的方法和属性值如图所示:其中,
一般来说,一个同步器是资源独占模式或者资源共享模式的其中之一,因此tryAcquire(int)和tryAcquireShared(int)只需要实现一个即可,tryRelease(int)和tryReleaseShared(int)同理。但是同步器也可以实现两种模式的资源获取和释放,从而实现独占和共享两种模式。
acquire(int)是获取资源的顶层入口,tryAcquire(int)是获取资源的方法,需要自定义同步器实现。addWaiter(Node.EXCLUSIVE)是将线程加入等待队列的尾部,acquireQueued(Node node, int arg)将线程阻塞在等待队列中,直到获取到资源后才返回。
release(int)是释放资源的顶层入口方法,tryRelease(int)是释放资源的方法,需要自定义同步器自己实现。unparkSuccessor(h)是唤醒后继节点的方法。
acquireShared(int)和releaseShared(int)是使用共享模式获取共享资源的顶层入口方法,tryAcquireShared(arg)是获取共享资源的方法,doAcquireShared(arg)将线程阻塞在等待队列中,直到获取到资源后才返回。releaseShared(int)是释放共享资源的顶层入口方法,doReleaseShared()方法释放共享资源。
面试问题模拟:AQS是接口吗?有哪些没有实现的方法?看过相关源码吗?
A:AQS定义了一个实现同步类的框架,实现方法主要有tryAquire和tryRelease,表示独占模式的资源获取和释放,tryAquireShared和tryReleaseShared表示共享模式的资源获取和释放。源码分析如上文所述。
Java并发系列 | Semaphore源码分析
在Java并发编程中,Semaphore(信号量)是AQS共享模式的实用工具,它能够控制多个线程对共享资源的并发访问,实现流量控制。Semaphore的核心概念是“许可证”,类似于公共汽车票,只有获取到票的线程才能进行操作。许可证数量有限,当数量耗尽时,后续线程需要等待,直到有线程释放其许可证。Semaphore构造器接受初始许可证数量,可以选择公平或非公平的获取方式。
Semaphore提供了获取和释放许可证的API,默认每次操作一个许可证。获取许可证有直接和尝试两种方式,直接获取可能阻塞,而尝试不会。acquire方法内部调用的是AQS的acquireSharedInterruptibly,它会尝试公平或非公平地获取,并在获取失败时决定是否阻塞。释放许可证则直接调用AQS的releaseShared方法,通过自旋循环确保同步状态的正确更新。
Semaphore的应用广泛,本文通过实现一个简单的数据库连接池,展示了Semaphore如何控制连接的并发使用。连接池初始化时创建固定数量的连接,每次线程请求连接时需要获取许可证,释放连接时则释放许可证。测试结果验证了Semaphore有效管理连接并发并确保了流量控制。
代码示例与测试结果表明,Semaphore通过控制许可证数量,确保了资源使用的合理调度,当连接池中所有连接被占用,后续请求将被阻塞,直到有连接被释放。这清楚地展示了Semaphore在并发控制中的作用。
2024-12-23 00:01
2024-12-22 23:11
2024-12-22 22:47
2024-12-22 22:47
2024-12-22 22:44
2024-12-22 22:30