前面已经说了很多Java并发和线程安全的东西,也提到并对比了内在锁和J.U.C包(java.util.concurrent包,后同)中Lock的锁。从这篇开始,对Java并发的整理从理论进入“实践”阶段,本篇对Lock、ReentrantLock和AbstractQueuedSynchronizer源码做简要分析和整理。先从Lock这个interface说起,然后分析ReentrantLock和AQS的实现。
0. 我们先看下Lock接口和ReentrantLock的大体实现。
- public interface Lock {
- void lock();
- void lockInterruptibly() throws InterruptedException;
- boolean tryLock();
- boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
- void unlock();
- Condition newCondition();
- }
可以看得出来,Lock能做这样几件事:
- 常规地获得锁
- 可中断地获得锁
- 尝试性获得锁,非阻塞
- 尝试性获得锁,如果超时则返回
- 解锁
- 生成和当前锁相关的条件(队列)对象
再来看下ReentrantLock的情况:
- 类声明,如下实现了Lock和Serializable接口
-
- public class ReentrantLock implements Lock, java.io.Serializable
- 属性,主要的属性只有内部类Sync的对象属性sync,ReentrantLock类的操作实际上都落在了sync身上
- 构造方法,有重载实现的两个,单参数的方法参数的含义为是否为公平锁,方法的实现就是构造一个Sync对象(根据公平参数,确定是FairSync还是NonfairSync)并赋值给sync,默认构造方法会调用参数为false的方法
- lock()和newCondition()调用sync的同名方法
- lockInterruptibly()调用sync的acquireInterruptibly(1)
- tryLock()调用sync的nonfairTryAcquire(1)
- 等待性tryLock()调用sync的tryAcquireNanos(1, unit.toNanos(timeout))
- unlock()调用sync的release(1)
除此之外,ReentrantLock是可重入锁,还有一些支持可重入的方法,这里不细说。可以说ReetrantLock是基于它的内部类Sync的对象来实现的,接下来看下Sync的类层次结构:
从eclipse中看,类层次结构一目了然,Sync被FairSync和Nonfair扩展,而父层有AbstactOwnableSynchronizer和AbstractQueuedSynchronizer。前者实现了当前同步器被哪个线程占有的逻辑,实现了get/setExclusiveOwnerThread()方法,确定获取当前互斥锁的Thread对象。后者则是java.util.concurrent包中非常重要的类,下面就重点来说说这个AbstractQueuedSynchronizer(AQS)。
1. AQS的队列结构和队列节点
从AbstractQueuedSynchronizer的名字就可以看得出来,这个类是抽象的队列的同步器。同步器不用说了;有关抽象的,以及具体如何和扩展的子类配合实现加锁和解锁,后面那段会具体描述;这里我们看看AQS的比较重要比较核心的部分,也就是状态处理和队列的实现。
从AQS类在eclipse的outline中,可以看出,除了序列化和具体的Unsafe底层操作相关的东西,AQS有三个最重要的属性和两个内部类:
- private volatile int state
- private transient volatile Node head
- private transient volatile Node tail
- static final class Node
- public class ConditionObject implements Condition, java.io.Serializable
其中state是当前的锁状态,通常(至少ReentrantLock是这么用的)这是锁是否被占用的一个重要标志,在ReentrantLock实现中是获得锁的重入线程数,0的时候是没有线程占用这个锁的。而和AQS实例绑定(就是非静态的内部类)的ConditionObject类是与条件对列相关的对象,后面细说。剩下的最重要就是Node静态内部类,也是构成队列的主要数据结构。其实此Node实现也并不复杂,就是通常的双向链表结构,有指向前后节点的引用,除此之外就是链表节点的数据部分,有如下属性字段:
- volatile int waitStatus。当前节点的状态,主要表示当前线程是获得锁、等待锁、在等待队列中等状态,对应于Node类中的几个常量
- volatile Thread thread。当前节点对应的线程对象。
- Node nextWaiter。AQS为每个条件对象单独维护了一个等待队列,依靠的就是这个属性引用。
在锁队列维护上,实际上是双向的。每次创建新节点,以当前线程为数据,nextWaiter指向互斥常量或共享常量。新增结点时,获取tail,并设置新节点的prev为tail,并尝试原子操作设置新节点为tail节点,如果tail结点为空或者设置tail结点出问题则调用enq方法循环尝试,其中为空 状态时,则new一个空Node为head,并让tail=head。
出队列的操作实际上是和线程相关的,在阻塞等待获得锁的过程中或者是执行condition的await()时,调用acquireQueued()方法,循环比较当前线程结点的上一个结点是不是head并调用tryAcquire()。如果成功,则设置当前node为head,并解除当前node向前以及前一个结点指向当前node的引用(设置为null),这样前一个结点就失去了引用链上的引用。第一次出队列的是首次初始化队列时创建的空Node对象,后面依次是之前被解锁的线程对应的node。当然,如果tryAcquire()不成功,则会将判断当前node的状态,如果是0则设置为SIGNAL常量并用LockSupport的park()方法挂起当前线程。
2. Sync和AQS的配合以及ReentrantLock的lock()和unlock()实现。
前面简单说到过,ReentrantLock的lock方法调用了sync的lock()方法,而不管是公平实现(FairSync)还是非公平实现(NonfairSync),所做的主要工作都是调用AQS的acquire()方法。而unlock()方法更直接,调用的是AQS的release()方法。
更进一步,对于acquire()和release()方法,所做的大概操作有两样,一个是调用名字为try开头的方法,即tryAcquire()和tryRelease()等,此外就是做队列和线程相关的操作。而对于AQS,有如下五个方法是未完整实现,需要扩展的子类进行定义的:
- protected boolean tryAcquire(int arg)
- protected boolean tryRelease(int arg)
- protected int tryAcquireShared(int arg)
- protected boolean tryReleaseShared(int arg)
- protected boolean isHeldExclusively()
结合ReentrantLock及其内部类Sync(以NonfairSync为例)的实现,主要是tryAcquire()和tryRelease(),我们看下如何构造锁操作。
当加锁时,调用acquire()方法,acquire()会尝试原子操作tryAcquire()。这个方法在非公平实现中,主要是通过AQS的state来检查和维护锁状态,如果state是0,说明没有线程占有这个锁,如果不为0并且锁的占有线程是当前线程,则是重入的情况,均可以获得锁并修改state值。如果是首次获得锁,则设置锁占有线程为当前线程。当然,如果前面两种情况都不满足,说明尝试获得锁失败,需要做前面段落所述的队列操作,创建一个等待结点并进入循环,循环中的park()调用挂起当前线程。
当解锁时,做对应而相反的操作。release()调用tryRelease()方法,如果修改state值成功,则找到队列中应该唤起的结点,对节点中的线程调用unpark()方法,恢复线程执行。这个操作在被恢复执行线程acquireQueued()方法的循环中完成,释放头结点并返回是否中断的状态,继续执行。
3. Lock的五个特点方面:尝试性非阻塞获得锁可中断、时间调度、公平性、一对多。
下面在简单介绍下ReentrantLock比起内在的synchronized锁的一些优秀特点的实现:
- 基于Unsafe的原子操作来修改state的状态,无论成功失败都会直接返回,这保证了非阻塞方式尝试获得锁
- 可中断和允许时间调度,则是利用了Unsafe的park方法的特性,park掉的线程是可以响应中断被唤醒的,而park的带有时间参数的重载方法则保证了时间调度性
- 公平和非公平实现,这个是在ReentrantLock的Sync的子类中实现的,主要的区别就是公平锁保证了队列的第一个节点先获得锁,而非公平不保证这点
- 至于一对多,貌似也没什么可多说的,一个类中可以有多个ReentrantLock类对象属性,自然就可以有多个锁,每个对象单独维护一个state属性
4. Condition的实现。
至于条件队列的实现,前文也多少提到了一些。AQS有个实现了Condition接口的内部类ConditionObject,其复用了锁队列的Node结点,单独为每个条件维护了一个单向链表队列。
当await()时,创建一个状态为CONDITION常量的Node类结点,释放当前线程的锁,并进入一个循环。这个循环退出的条件是结点已经被放到锁队列上或者是检测到了中断做中断处理,循环的内容就是不断的去park()掉当前线程。当循环退出后尝试重新获得锁,以继续执行等待后的代码。
而signal()/signalll()方法更好理解,主要操作就是将一个或者多个Node对象的状态设置为0,并将该节点加入获取锁的队列中,恢复线程。
本文对java.util.concurrent.locks的可重入锁机制和AQS进行了比较详细的分析,后续也有可能会对ReentrantReadWriteLock和Semaphore做分析。更详细的逻辑还请参照JDK的源码
相关推荐
在本篇中,我们将深入分析AQS的条件队列,它是实现高级同步机制如`ReentrantLock`和`CountDownLatch`的关键部分。 条件队列是AQS中与`Condition`接口相关的部分,它允许线程在满足特定条件时等待,而不是简单地阻塞...
本文将对ReentrantLock类的源码进行详细分析,涵盖ReentrantLock的继承关系、构造方法、锁机制、加锁和解锁机制等方面。 ReentrantLock的继承关系 ReentrantLock类继承自AbstractOwnableSynchronizer,...
### ReentrantLock源码分析 #### 一、ReentrantLock简介 ReentrantLock是一个基于`AbstractQueuedSynchronizer`(AQS)实现的高级锁工具类。与传统的synchronized关键字相比,ReentrantLock提供了更多控制手段,比如...
**Java并发系列之AbstractQueuedSynchronizer源码分析概要** **1. AbstractQueuedSynchronizer(AQS)的定义与作用** AbstractQueuedSynchronizer(AQS)是Java并发编程中的核心组件,它是一个抽象的、基于FIFO...
下面对这三种机制进行详细的分析和比较。 一、Synchronized Synchronized 是 Java 中最基本的同步机制,它可以用来同步方法或代码块。Synchronized 的实现是基于锁机制的,它会锁定一个对象的监视器,以便防止其他...
《ReentrantLock源码详解与应用》 ReentrantLock,可重入锁,是Java并发编程中一个重要的锁实现,它提供了比...理解ReentrantLock的源码有助于我们更好地掌握并发编程中的锁机制,以优化并发性能和避免死锁等问题。
本篇文章将深入解析ReentrantLock的源码,重点讨论非公平锁和公平锁的获取过程。 1. **ReentrantLock的基本概念** ReentrantLock是由Java提供的可重入互斥锁,支持公平锁和非公平锁两种模式。非公平锁的特性是获取...
根据给定文件的信息,我们可以深入理解AQS(AbstractQueuedSynchronizer)独占锁之ReentrantLock的源码分析及其实现原理。这不仅包括ReentrantLock本身的特性,还包括了其背后的AQS框架是如何工作的。 ### 一、管程...
在本文中,我们将深入分析`ReentrantLock`的`lock()`方法,理解其内部机制,包括锁的获取、释放以及公平性和非公平性的实现。 首先,`ReentrantLock`的`lock()`方法很简单,它只是调用了内部类`Sync`的`lock()`方法...
【Java并发系列之AbstractQueuedSynchronizer源码分析(独占模式)】 AbstractQueuedSynchronizer(AQS)是Java并发编程中一个重要的工具,它是Java并发包`java.util.concurrent.locks`中的核心抽象类,用于构建锁...
ReentrantLock Lock 加锁过程源码分析图,AQS 源码分析
ReentrantLock lock方法注释
8. Lock接口 (ReentrantLock 可重入锁) 特性 ReentantLock 继承接口 Lock 并实现了接口中定义的方法, 它是一种可重入锁, 除了能完成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁...
ReentrantLock源码详解--条件锁 ReentrantLock源码详解中最重要的一个部分就是条件锁,条件锁是指在获取锁之后发现当前业务场景自己无法处理,而需要等待某个条件的出现才可以继续处理时使用的一种锁。今天我们来...
Java并发系列之ReentrantLock源码分析 ReentrantLock是Java 5.0中引入的一种新的加锁机制,它实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。ReentrantLock的底层实现是通过AQS来实现多线程同步...
在高级并发编程中,`ReentrantLock`是一个强大的工具,相较于内置的`synchronized`关键字,它提供了更多的灵活性和控制。本篇文章将深入探讨`ReentrantLock`的使用以及如何结合Lambda表达式来优化同步代码。 `...
ReentrantLock源码详解--公平锁、非公平锁 ReentrantLock是一种重入锁,实现了Lock接口,能够对共享资源重复加锁,即当前线程获取该锁再次获取不会被阻塞。ReentrantLock的可重入性是通过继承AQS...
对于`ReentrantLock`,可以通过`lock()`和`unlock()`方法控制这一过程。线程在进入同步代码块之前调用`lock()`,退出时调用`unlock()`。由于是手动控制,所以需要注意确保解锁次数与加锁次数一致,否则可能导致死锁...
通过对ReentrantLock的源码分析,我们可以更好地理解ReentrantLock的实现机制和使用场景,从而更好地应用于实际开发中。 知识点: 1. ReentrantLock是一个可重入锁,它和synchronized的方法和代码有着相同的行为和...
- `ReentrantLock`提供`lock()`和`unlock()`方法来加锁和解锁。 - 支持公平锁(通过构造函数传入`true`)和非公平锁(默认)。 3. **高级功能**: - 可以尝试获取锁:`tryLock()`方法可以在无法立即获取锁时返回...