ReentrantLock源码分析与理解
在上面一篇分析ThreadExecutedPool的文章中我们看到线程池实现源码中大量使用了ReentrantLock锁,那么ReentrantLock锁的优势是什么?它又是怎么实现的呢?
ReentrantLock又名可重入锁,为什么称之为可重入锁呢?简单来说因为它允许一个线程多次取获得该锁,不过多次获取该锁之后,也需要执行同样次数的释放锁操作,否则该锁将被当前线程一直持有,导致其它线程无法获取。需要注意的是,释放锁的操作需要我们用代码来控制,它并不会自动取释放锁。在ReentrantLock中实现了两种锁fairSync和NonfairSync,即公平锁和非公平锁,今天我们就来聊聊ReentrantLock中nonfairSync锁的实现。
废话不多说,下面开始分析代码!
1、1 Lock()
首先看一下lock()方法,这个方法非常重要,它也是我们获取锁的入口:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
ReentrantLock锁的初始状态为0,compareAndSetState方法将尝试获取锁并将当前锁的状态设置为1。如果成功获取了锁会调用setExclusiveOwnerThread()方法设置当前线程拥有该锁的独占访问权。
如果调用compareAndSetState()获取锁失败,则返回false并执行acquire(1)。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
我们看到acquire(1)的代码中有一条if语句,当tryAcquire(1)返回false以及acquireQueued(addWaiter(Node.EXCLUSIVE), arg)返回true时,才会去执行selfInterrupt();方法。下面我们来看看tryAcquire(1)和acquireQueued(addWaiter(Node.EXCLUSIVE), arg)这两个家伙干了什么。
先看一下tryAcquire()方法。
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
tryAcquire方法会去调用nonfairTryAcquire()方法。
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()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
1、首先调用getState()方法获取当前锁状态,如果锁状态为0。表示当前锁没有被其他线程占用,这里会再次尝试去获取锁。如果成功的拿到了锁,将设置锁的拥有者为当前线程,同时返回true。如果此时返回true的话,表示当前线程成功获取到了锁,lock()方法调用成功。
2、如果当前锁状态不为0,判断当前线程是否为锁的拥有者,如果是的话,尝试将当前锁的状态值加acquires。如果当前neextc的值小于0,抛出异常。若不小于0,将当前锁的值设置为nextc。为什么说ReentrantLock为可重入锁,就体现在这里了,如果当前线程为锁的拥有者,该线程再次调用lock方法时,当前锁的状态值会加1。当然我们释放该锁的时候,也要调用相应的unlock()方法,以使得锁的state值为0,可被其他线程请求。
3、如果当前锁的值不为0且拥有锁的线程也不为当前线程则返回false。也就是tryAcquire()再次获取锁并没有成功。
值得注意的是,既然再第一次调用compareAndSetState()的时候,已经获取失败了为什么还要再调用tryAcquire()方法再获取一次呢?我们可以理解为这是一种保险机制,如果此时无法获取锁,我们将会将当前线程加入到阻塞队列中挂起等待后面被唤醒重新争夺锁。
回顾一下上面的if判断条件
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
如果tryAcquire()的返回值为false,那么接下来会执行acquireQueued(addWaiter(Node.EXCLUSIVE),arg)方法。这个方法看起来比较复杂,它在acquireQueued()方法又调用了addWaiter()方法,我们先来看看addWaiter()方法:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
首先我们创建了一个包含了当前线程的Node节点,并将tail(tail节点即是尾节点)赋值给pred节点。如果我们第一次进来,那么tail节点肯定为空,将会去执行enq(node)方法。如果tail不为空,那么接下来的三句代码干了什么呢?
先回忆一下,如果我们希望在一个双向链表的尾部新增一个节点,应该如何操作,大致应该有如下三步:
- node.prev = pred; node节点的前驱指向尾节点
- pred.next = node; 将尾节点的后继设置为当前节点
- tail = node; 将node节点设置为尾节点
我们再看一下详细代码:
- node.prev=pred; 当前pred节点代表的是尾节点,也就是说设置node节点的前驱为当前尾节点。
- if (compareAndSetTail(pred, node)),我们看下compareAndSetTail(pred, node)方法。
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
compareAndSetTail的原理其实就是CAS算法,将期望值和内存地址为tailOffset上的值进行比较,如果两者相同,则更新tailOffset上的值为最新值update。其实也就是如果tailOffset上的值和pred(老的尾节点)的值相同,则将尾节点更新为新的node节点。
- 将原尾节点的后继设置为当前节点。
其实上面三步实现的功能和在双向链表尾部新增一个节点的功能大致相同,只是顺序略有调整。
接下来看一下enq(node)方法
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq代码中有一个死循环for(;
ReentrantLock源码分析与理解的更多相关文章
- JUC AQS ReentrantLock源码分析
警告⚠️:本文耗时很长,先做好心理准备,建议PC端浏览器浏览效果更佳. Java的内置锁一直都是备受争议的,在JDK1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6 ...
- ReentrantLock 源码分析以及 AQS (一)
前言 JDK1.5 之后发布了JUC(java.util.concurrent),用于解决多线程并发问题.AQS 是一个特别重要的同步框架,很多同步类都借助于 AQS 实现了对线程同步状态的管理. A ...
- Java并发编程-ReentrantLock源码分析
一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ...
- java多线程---ReentrantLock源码分析
ReentrantLock源码分析 基础知识复习 synchronized和lock的区别 synchronized是非公平锁,无法保证线程按照申请锁的顺序获得锁,而Lock锁提供了可选参数,可以配置 ...
- ReentrantLock源码分析--jdk1.8
JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8AQS源码分析--jdk1.8ReentrantLock源码分 ...
- JUC之ReentrantLock源码分析
ReentrantLock:实现了Lock接口,是一个可重入锁,并且支持线程公平竞争和非公平竞争两种模式,默认情况下是非公平模式.ReentrantLock算是synchronized的补充和替代方案 ...
- Java并发编程之ReentrantLock源码分析
ReentrantLock介绍 从JDK1.5之前,我们都是使用synchronized关键字来对代码块加锁,在JDK1.5引入了ReentrantLock锁.synchronized关键字性能比Re ...
- Java并发系列[5]----ReentrantLock源码分析
在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...
- ReentrantLock 源码分析从入门到入土
回答一个问题 在开始本篇文章的内容讲述前,先来回答我一个问题,为什么 JDK 提供一个 synchronized 关键字之后还要提供一个 Lock 锁,这不是多此一举吗?难道 JDK 设计人员都是沙雕 ...
随机推荐
- 重新认识一个强大的 Gson
从一个 Bug 说起 不知道你们发现没有,你写完的程序无论当时怎么测试,过一段时间总会出 Bug .再说一个每天都在发生的例子:在你写完一篇博客后,立即检查的话,总是查不出自己写的错别字. 据说这些都 ...
- Ant学习总结2
<?xml version="1.0" encoding="UTF-8"?> <project default= "compile& ...
- python2.7读汉字的时候出现乱码,如何解决
我使用的是window系统,安装Anaconda,所以用的是. 1.数据形式 2.读进来乱码 3.解决办法 3.1方法1 import pandas as pd #pd.read_csv('c:/us ...
- windows下读取utf-8文件
#include <stdio.h> #include <tchar.h> #include <memory> int main() { FILE* fp1 = f ...
- c++ 调用dl里的导出类
来源:http://blog.csdn.net/yysdsyl/article/details/2626033 动态dll的类导出:CPPDll2->test.h #pragma once // ...
- tornado学习 - TCPServer 实现聊天功能
最近学习tornado框架,其中有很多值得学习的模块,鉴于某位学长的建议,也决定好好看看tornado.tcpserver. 关于TCP协议详细介绍可查阅wiki. 对于TCP服务器,基本的操作tor ...
- Zabbix简介及安装
前言 未经监控就上线的系统,就是裸奔,耍流氓. 监控对我们来说太重要了,监控工具也有很多,像,Cacti,Nagios,Ganglia,Zabbix等等,今天我们要介绍的是Zabbix,它的功能很强大 ...
- mac环境下mentohust锐捷登录配置
今天测试react native嵌入原生项目,账号流量恰好用完,想换同学账号却不会更改配置,以至于被网络弄得头疼了一中午.于是,好好研究了一下这个mentohust 一.需要下载的文件 我已经全部整理 ...
- 前端安全(XSS、CSRF防御)
一.网络安全 OWASP:开放式Web应用程序安全项目(OWASP,Open Web Application Security Project) OWASP是一个开源的 ...
- Ubuntu 小白安装血泪史
介绍: 新入手的Ubuntu:版本 命令行模式下 出现 ♦♦♦♦ 图形界面无法获取最高权限:gurb.cfg 无法再图形界面下修改 安装类型: 1.安装Ubuntu,与Window 7共存 2.清除整 ...