ReentrantLock获取到非公平锁的源码
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
为了表述清楚假设当前请求获取锁的线程名称为“线程x”
(1)非公平锁的第一步:当“线程x”请求获取到锁的时候,直接调用compareAndSetState(0, 1),这个方法会将判断state=0?如果等于0,说明这个锁没有被占领,那么“线程x”直接获取,这就是非公平锁,竞争锁成功之后,就将属性Thread exclusiveOwnerThread设置为“线程x”。
(2)如果没有获取到,那么就调用 acquire(1);
一: acquire(1);
这个方法首先调用tryAcquire方法获取锁,如果获取不到就要把“线程x”加入到队列中,并且阻塞“线程x”。
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
各个方法定义如下:
1、tryAcquire:去尝试获取锁,获取成功则设置锁状态并返回true,否则返回false。该方法由NonfairSync自己实现。
2、addWaiter:如果tryAcquire返回FALSE(获取锁失败),则调用该方法将“线程x”加入到CLH同步队列尾部。
3、acquireQueued:“线程x”加入到队列之后就会调用这个方法将“线程x”挂起,直到获取到(许可)permit之后,如果线程被打断了,则将“线程x”从队列中移除。
4、selfInterrupt:如果是产生了中断,则返回true,否则返回false。
二、公平锁和非公平锁都对tryAcquire(arg)方法有各自的实现。查看非公平锁的实现。
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {(1)
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {(2)
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
这个方法有三个逻辑判断:
(1)如果没有线程占领锁,即c==0,那么竞争锁。
(2)如果锁被占领,判断锁是否被“线程x”占领,是就计算重入一次,并且返回true
(3)当锁被非“线程x”占领,就返回false.
三、 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
“线程x”竞争锁失败之后来到这个方法,这个方法首先执行的是addWaiter,将“线程x”加入到队列的尾端。
3.1、addWaiter(Node.EXCLUSIVE)
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
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;
}
这个方法包含两个部分,enq(node)以上是一个部分,enq(node)是一个部分。第一部分是一个快速入队列的方法,当只有一个线程来请求锁的时候或者请求的方式是异步方式的时候,可以实现线程节点一个一个进入队列。enq(node)是给多个线程竞争入队列的。这部分在我的下载文件中,有详细的画图说明,看不明白可以下载看看。
3.2、enq(node)
这个方法是将节点入队,是一个死循环,只有把节点添加到队列中之后才会返回,返回的是添加节点的前置节点。在这个方法中包括了一个节点初始化的部分,就是创建头节点和尾节点。
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
3.3、acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();(1)
if (p == head && tryAcquire(arg)) {(2)
setHead(node);(3)
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&(4)
parkAndCheckInterrupt())(5)
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);(6)
}
}
根据标号分析每个部分的作用:
(1)“线程x”添加到队列之后,返回的是“线程x”节点。(1)的作用是取到“线程x”节点的前一个节点,这里假设是“线程y”
(2)如果“线程y”是头节点,并且“线程x”获取锁成功,则执行(3),把这个“线程x”节点设置成头节点,并且更改当前线程节点的thread=null,prev=null,这里其实就是把当前节点更改成了头节点的状态了。
(4)如果“线程y”节点不是头节点(就是说前面已经有其他线程节点来请求锁,并且加入到队列中,又阻塞了)或者“线程x”没有获取锁成功,那么执行(4)shouldParkAfterFailedAcquire的源码如下:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;// 获取前置节点“线程y”的waitStatus
if (ws == Node.SIGNAL) // 如果ws ==-1
/*
* Node.SIGNAL的作用就是,当前的线程节点释放锁之后,可以顺利的通知它的继任线程节点(这个会在释放锁的时候说明),让他取获取锁
*这里问题就是,如果继任节点还在请求锁的话可以正常获取到,一旦继任节点被中断或者抛出异常了,怎么把这个节点移除队列?
*
*/
return true;
if (ws > 0) {
/*
*如果ws>0,目前来看只有CANCELLED。那么就会就要往“线程y”节点的前面去找一个节点,
*直到这个节点的pred.waitStatus <=0,
*然后 pred.next = node;把当前的节点赋值给找到的这个节点的next
*如果执行了这个方法,那么“线程x”的前置节点就会改变,不会再是“线程y”节点
*这里假设“线程y”节点ws = -1
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*ws必须等于0或者-3.表示我们需要一个信号,但是不是停止。调用者必须再次确认当前的节点在停止前必须时没有获取到锁的。
*这里就是又要跳出当前的方法去执行死循环了,下次还会再进来,知道返回true,那个时候才会去执行parkAndCheckInterrupt
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
(5) parkAndCheckInterrupt(),当确定“线程x”节点需要挂起的时候,那么就会执行这个方法去挂起“线程x”节点。
(6)cancelAcquire(node);这个方法的作用就是当“线程x”节点出现异常获取被终端之后把它从队列中移除。
总结:
第一次看源码,建议先使用一下重入锁(ReentrantLock),然后带着下面几个问题看源码,并且从源码中找答案:
1、使用一个自己可以调试源码的IDE工具,我使用的intellij,写测试类调试源码很方便?
2、ReentrantLock是重入锁,那么实现的原理是什么?
3、重入锁是什么意思,应用场景是什么,怎么实现的?
4、公平锁与非公平锁是什么意思?为什么公平与不公平?
5、线程是怎么竞争获取到锁的?CAS指的是什么?
6、锁的排他模式与共享模式?
7、线程怎么被封装成node,并且通过什么方式加入到队列中?(这个部分最好画图,而且要考虑到各个线程之间的竞争入队)?
8、线程使用完锁之后,怎么传递给下一个线程使用?
ReentrantLock获取到非公平锁的源码的更多相关文章
- 从ReentrantLock实现非公平锁的源码理解AQS中的CLH队列
虽然前面也看过AQS的文章,并且转载过一篇大佬的分析,但是我觉得他们对于AQS和ReentrantLock部分的源码的分析并不详细,自己理解期来还是有问题,于是自己准备花时间重新梳理下,好了,进入正题 ...
- ReentrantLock 如何实现非公平锁?和公平锁实现有什么区别
reentrant 英[riːˈɛntrənt] 美[ˌriˈɛntrənt] 先学会读.单词原意是可重入的 考察显示锁的使用.可延伸知识点 独占锁 & 共享锁 独占锁 - 悲观锁(不能同时被 ...
- 图解AQS原理之ReentrantLock详解-非公平锁
概述 并发编程中,ReentrantLock的使用是比较多的,包括之前讲的LinkedBlockingQueue和ArrayBlockQueue的内部都是使用的ReentrantLock,谈到它又不能 ...
- 理解ReentrantLock的公平锁和非公平锁
学习AQS的时候,了解到AQS依赖于内部的FIFO同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成一个Node对象并将其加入到同步队列,同时会阻塞当 ...
- ReentrantLock中的公平锁与非公平锁
简介 ReentrantLock是一种可重入锁,可以等同于synchronized的使用,但是比synchronized更加的强大.灵活. 一个可重入的排他锁,它具有与使用 synchronized ...
- lesson3.1:java公平锁和非公平锁及读写锁
关于这四种锁的各自情况,网上有很多文章做了介绍,本不想单独开章节介绍,本章只介绍这四种锁的一些源码特点及注意事项. demo 源码:https://github.com/mantuliu/javaAd ...
- Java并发指南8:AQS中的公平锁与非公平锁,Condtion
一行一行源码分析清楚 AbstractQueuedSynchronizer (二) 转自https://www.javadoop.com/post/AbstractQueuedSynchronizer ...
- 带你快速了解Java锁中的公平锁与非公平锁
前言 Java语言中有许多原生线程安全的数据结构,比如ArrayBlockingQueue.CopyOnWriteArrayList.LinkedBlockingQueue,它们线程安全的实现方式并非 ...
- 一分钟理解Java公平锁与非公平锁
本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...
随机推荐
- Perl注释文本的高亮显示规则
sub help{ print <<EndOfUsage;\e[1;37mHELP :1. Usage : perl $0 input output 2. Function : tran ...
- word-break:break-all; 和 word-wrap:break-word 换行
word-break:break-all; 和 word-wrap:break-word;两种写法都是让英文句子在父级宽度不够的情况下换行. 两个属性都同样是让文字换行,但存在着细微的区别,大部分时候 ...
- ABP框架系列之七:(About-关于ABP)
Considerations Source codes Contributors Contact ASP.NET Boilerplate is designed to help us to devel ...
- py-opp 类(class)
类的创建 class 类名: pass class Bar: def foo(self,arg): # self ,是传的实例对象, print('self:',self,arg) #因为类属性和方法 ...
- HDU 2138 How many prime numbers (判素数,米勒拉宾算法)
题意:给定一个数,判断是不是素数. 析:由于数太多,并且太大了,所以以前的方法都不适合,要用米勒拉宾算法. 代码如下: #include <iostream> #include <c ...
- 20155226 2016-2017-2 《Java程序设计》第7周学习总结
20155226 2016-2017-2 <Java程序设计>第7周学习总结 教材学习内容总结 认识时间与日期 六个时间基准: 1.格林威治标准时间 2.世界时 3.国际原子时 4.世界协 ...
- Spring 、 CXF 整合 swagger 【试炼】
官网:http://swagger.io/ http://swagger.io/specification/ 上面就是描述了什么是 SWAGGER OBJECT 2. 如何用jax-rs 注解方式产生 ...
- MFC框架仿真<二>
- Paper格式-国际会议版
Paper Title 论文题目 Authors Name/s per 1st Affiliation (Author) 作者名字/s 每第一作者 line 1 (of Affiliation): d ...
- C++ 11可变参数接口设计在模板编程中应用的一点点总结
概述 本人对模板编程的应用并非很深,若要用一句话总结我个人对模板编程的理解,我想说的是:模板编程是对类定义的弱化. 如何理解“类定义的弱化”? 一个完整的类有如下几部分组成: 类的名称: 类的成员变量 ...