目录
- ReentrantLock简介
- 基础知识铺垫
- state属性
- 线程持有者属性
- ReentrantLock中的队列使用
- Demo&原理解析
- 公平锁-lock()方法
- Demo
- 详尽原理
- 白话原理(面试口述)
- unLock()方法
- 详尽原理
- 白话原理(面试口述)
- 公平锁-lock()方法
- Demo
- 详尽原理
- 白话原理(面试口述)
- lockInterruptibly()方法
- Demo
- 详尽原理
- 白话原理(面试口述)
- tryLock()方法
- Demo
- 详尽原理
- 白话原理(面试口述)
- tryLock(long timeout, TimeUnit unit)方法
- Demo
- 详尽原理
- 白话原理(面试口述)
- 公平锁-lock()方法
- 面试题汇总
- ReentrantLock全中文注释可运行源码下载
原文地址:https://gitbook.cn/gitchat/activity/5d80202b5fb33e7ca5da4437
ReentrantLock简介
jdk并发包中的可重入锁,是基于AQS(AbstractQueuedSynchronized)实现的,它有公平锁(线程上锁的顺序完全基于调用方法的先后顺序)和不公平锁(线程上锁的顺序不完全基于调用方法的先后顺序)两种实现方式。
ReentrantLock提供多种API:公平锁/非公平锁-lock()方法、定时释放锁-tryLock(long timeout, TimeUnit unit)方法、interrupt中断阻塞线程抛出InterruptedException异常、获取锁失败直接返回的tryLock()
本文基于open-jdk 1.8讲解
基础知识铺垫
state属性
//同步状态标识
private volatile int state;
state为0时,代表当前没有线程持有锁;为1时,代表有线程持有锁;如果大于1,因为ReentrantLock为重入锁,所以代表锁被当前线程重入的次数。
使用volatile,保证了state属性值的可见性(可见性就是在获得属性值时,总能保证是最新值的特性)。
线程持有者属性
//当前线程持有者标识
private transient Thread exclusiveOwnerThread;
该属性是Thread类型的,标识了锁的当前持有线程。
ReentrantLock中的队列使用
ReentrantLock中的队列是FIFO(先进先出),为了实现公平锁,保证线程的线程的加锁顺序,同时也是存储元素,那么这个队列的数据结构是怎样的呢?
在ReentrantLock中,定义了一个Node类,用来表示线程,同时也是链表的组成元素,Node的prev属性指向前一个节点(代表前一个进入队列的线程),next属性指向后一个节点(代表后一个进入队列的线程),这种方式形成了一个链表;AQS还维护了Node类型的head属性和tail属性,默认为null,分别表示头结点和尾节点,这两个属性为了让在后续逻辑中,能够很轻易的拿到头和尾节点,做出逻辑处理和判断。
以下是Node类的核心属性:
//指向前一个节点
volatile Node prev;
//指向后一个节点
volatile Node next;
//指向当前节点表示线程
volatile Thread thread;
/*
等待状态,针对 ReentrantLock ,共有3种
0:初始化默认状态或者是无效状态,即在成员变量定义int类型默认为0,或者表示已解锁
-1(SIGNAL):标记当前结点表示的线程在释放锁后需要唤醒下一个节点的线程,以当前值来标识是否要进行唤醒操作
1(CANCELLED):在同步队列中等待的线程未正常结束(发生中断异常或者其它不可预知的异常),标记为取消状态
*/
volatile int waitStatus;
Demo&原理解析
非公平锁-lock()方法
Demo
public void testReentrantLock() {
//多个线程使用同一个ReentrantLock对象,上同一把锁
Lock lock = new ReentrantLock();
lock.lock();
System.out.println("处理");
lock.unlock();
}
详尽原理
阅读详尽原理前,为加强理解,请对照源码阅读(本文末尾附带中文注释可运行源码下载链接)
进入lock方法后,就先调用cas方法抢占锁(将state从0修改为1),不管是否有线程在排队;如果修改成功,则更新当前线程持有者属性为当前线程,如果修改不成功,则调用acquire方法;
final void lock() {
//直接使用cas原子方法,如果state为0则修改为1,而不乖乖去FIFO(先进先出)队列排队
if (compareAndSetState(0, 1))
//如果上锁成功,将锁持有者修改为当前线程对象,上锁成功
setExclusiveOwnerThread(Thread.currentThread());
else
//如果失败,则执行以下逻辑(包括判断是否是线程重入、再次尝试上锁、阻塞线程、被唤醒获取锁等逻辑)
acquire(1);
}
进入acquire方法后,再尝试获取锁,如果再获取失败则初始化代表当前线程的节点,加入到队列当中去并阻塞,直到被唤醒(这里之所以要尝试2次获取动作,是为了充分发挥非公平锁性能优势)
public final void acquire(int arg) {
// 1.tryAcquire: 再次尝试上锁,true:标识上锁成功;false:标识上锁失败
if (!tryAcquire(arg) &&
/*
2.addWaiter:将线程封装成节点,放入FIFO队列中;
3.acquireQueued:
自旋获取锁,如果再次尝试获取锁失败,则阻塞线程;
等待队列中的前一个线程解锁后,唤醒本线程,成功获取锁
*/
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//将线程的状态置为中断(该代码的作用在acquireQueued里讲)
selfInterrupt();
}
进入1.tryAcquire后,如果此时无线程持有锁,则再次尝试获取锁:1.如果获取失败,进入队列;2.否则返回获取成功;如果有线程持有锁,则判断是否是当前线程持有锁:1.如果是,则累加重入数;2.如果不是,进入队列
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//如果当前没有线程持有锁,则再次尝试获取锁,获取锁成功后,修改当前锁持有者,返回上锁成功
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//当前锁的持有者正是当前线程,则累加重入数,返回上锁成功
else if (current == getExclusiveOwnerThread()) {
//之所以使用不支持原子性的操作进行赋值,是因为只有当前拥有锁的线程才能修改这个state,所以不会发生其他线程修改
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
进入2.addWaiter后,创建代表当前线程的节点并追加链表末尾
/**
* 描述:初始化节点并追加链表末尾
*
* @param mode 标识当前线程的节点是独占模式还是共享模式,对代码逻辑没有实际意义
* @return 代表当前线程的节点
*/
private Node addWaiter(Node mode) {
//初始化链表中的对象NOde,代表当前线程
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
//如果链表已经初始化(最后一个节点不为空),则直接将当前节点放在尾节点的后面接口
if (pred != null) {
//将node的prev属性赋值为之前链表的尾结点
node.prev = pred;
// 使用原子方法,tail变量赋值成node节点
// (注:这里只修改了记录了尾节点的变量,并没有修改链表节点间的关联关系)
if (compareAndSetTail(pred, node)) {//#1
//将之前链表的尾结点的next属性赋值为node节点(这里之所以没有使用cas原子方法是因为其它线程想要修改t的next属性都必须成功获取t,而t是只有在t所代表节点是尾节点的那个时间点,成功执行compareAndSetTail(t, node)的线程才能够拥有的)
pred.next = node;
return node;
}
}
//如果链表还没有初始化或者因为锁竞争激烈,#1处的compareAndSetTail执行失败,将会对链表进行初始化或者自旋直到compareAndSetTail执行成功
enq(node);
return node;
}
/**
* 如果链表还没有初始化或者因为锁竞争激烈,#1处的compareAndSetTail执行失败,将会对链表进行初始化或者 自旋 直到compareAndSetTail执行成功
*/
private Node enq(final Node node) {
来源:https://blog.csdn.net/n752543926/article/details/100743154