ReentrantLock 源码分析以及 AQS (一)

本文介绍了AQS(AbstractQueuedSynchronizer)的基础概念,重点分析了ReentrantLock如何利用AQS实现独占锁,包括非公平锁和公平锁的获取与释放过程。通过源码解析,阐述了AQS同步队列的结构以及线程的入队、出队和中断响应。此外,对比了公平锁与非公平锁的区别,讨论了各自的优缺点。文章结尾提到ReentrantLock的释放锁机制,并预告了下篇将探讨AQS的共享锁实现。
摘要由CSDN通过智能技术生成

前言

JDK1.5 之后发布了JUC(java.util.concurrent),用于解决多线程并发问题。AQS 是一个特别重要的同步框架,很多同步类都借助于 AQS 实现了对线程同步状态的管理。

AQS 中最主要的就是独占锁和共享锁的获取和释放,以及提供了一些可中断的获取锁,超时等待锁等方法。

ReentranLock 是基于 AQS 独占锁的一个实现。ReentrantReadWriteLock 是基于 AQS 共享锁的一个读写锁实现。本来打算一篇文章里面写完独占锁和共享锁,但是发现篇幅太长了,也不易于消化。

因此,本篇就先结合 ReentrantLock 源码,分析 AQS 的独占锁获取和释放。以及 ReentrantLock 的公平锁和非公平锁实现。

下一篇再写 ReentrantReadWriteLock 读写锁源码,以及 AQS 共享锁的获取和释放。

在正式讲解源码之前,墙裂建议读者做一些准备工作,最好对以下知识有一定的了解,这样阅读起来源码会比较轻松(因为,我当初刚开始接触多线程时,直接看 AQS 简直是一脸懵逼,就像读天书一样。。)。

  1. 了解双向链表的数据结构,以及队列的入队出队等操作。
  2. LockSupport 的 park,unpark 方法,以及对线程的 interrupt 几个方法了解(可参考:LockSupport的 park 方法是怎么响应中断的?)。
  3. 对 CAS 和自旋机制有一定的了解。

AQS 同步队列

AQS 内部维护了一个 FIFO(先进先出)的双向队列。它的内部是用双向链表来实现的,每个数据节点(Node)中都包含了当前节点的线程信息,还有它的前后两个指针,分别指向前驱节点和后继节点。下边看一下 Node 的属性和方法:

static final class Node {
	//可以认为是一种标记,表明了这个 node 是以共享模式在同步队列中等待
	static final Node SHARED = new Node();
	//也是一种标记,表明这个 node 是以独占模式在同步队列中等待
	static final Node EXCLUSIVE = null;

	/** waitStatus 常量值 */
	//说明当前节点被取消,原因有可能是超时,或者被中断。
	//节点被取消的状态是不可逆的,也就是说此节点会一直停留在取消状态,不会转变。
	static final int CANCELLED =  1;
	//说明后继节点的线程被 park 阻塞,因此当前线程需要在释放锁或者被取消时,唤醒后继节点
	static final int SIGNAL    = -1;
	//说明线程在 condition 条件队列等待
	static final int CONDITION = -2;
	//在共享模式中用,表明下一个共享线程应该无条件传播
	static final int PROPAGATE = -3;

	
	//当前线程的等待状态,除了以上四种值,还有一个值 0 为初始化状态(条件队列的节点除外)。
	//注意这个值修改时是通过 CAS ,以保证线程安全。
	volatile int waitStatus;

	//前驱节点
	volatile Node prev;

	//后继节点
	volatile Node next;

	//当前节点中的线程,通过构造函数初始化,出队时会置空(这个后续说,重点强调)
	volatile Thread thread;

	//有两种情况。1.在 condition 条件队列中的后一个节点 
	//2. 一个特殊值 SHARED 用于表明当前是共享模式(因为条件队列只存在于独占模式)
	Node nextWaiter;

	//是否是共享模式,理由同上
	final boolean isShared() {
		return nextWaiter == SHARED;
	}

	//返回前驱节点,如果为空抛出空指针
	final Node predecessor() throws NullPointerException {
		Node p = prev;
		if (p == null)
			throw new NullPointerException();
		else
			return p;
	}

	Node() {    // Used to establish initial head or SHARED marker
	}

	Node(Thread thread, Node mode) {     // Used by addWaiter
		this.nextWaiter = mode;
		this.thread = thread;
	}

	Node(Thread thread, int waitStatus) { // Used by Condition
		this.waitStatus = waitStatus;
		this.thread = thread;
	}
}

另外,在 AQS 类中,还会记录同步队列的头结点和尾结点:

    //同步队列的头结点,是懒加载的,即不会立即创建一个同步队列,
	//只有当某个线程获取不到锁,需要排队的时候,才会初始化头结点
    private transient volatile Node head;

	//同步队列的尾结点,同样是懒加载。
    private transient volatile Node tail;

独占锁

这部分就结合 ReentrantLock 源码分析 AQS 的独占锁是怎样获得和释放锁的。

非公平锁

首先,我们从 ReentrantLock 开始分析,它有两个构造方法,一

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值