ReentrantLock 0

关于ReentrantLock的文章其实写过的,但当时写的感觉不是太好,就给删了,那为啥又要再写一遍呢

最近闲着没事想自己写个锁,然后整了几天出来后不是跑丢线程就是和没加锁一样,而且五六段就一个cas性能很差,感觉离大师写的差十万八千里

于是!我就想重新研究研究看看大师咋写的,这篇博客也算个笔记吧,这篇看的是ReentrantLock的公平锁,准备写个两三篇关于ReentrantLock 就这两天写!

这篇博客完全个人理解,如果有不对的地方欢迎您评论或者私信我,我非常乐意接受您的意见或建议

CAS

首先要知道,ReentrantLock是基本都是在java代码层面实现的,而最主要的一个东西就是CAS compare and swap 比较并交换

这个操作可以看成为是一个原子性的,在java中可以使用反射获取到Unsafe类来进行cas操作

public test() {
try {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
if (!unsafeField.isAccessible()) {
unsafeField.setAccessible(true);
}
unsafe = (Unsafe) unsafeField.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}

Park

在juc包下LockSupport类中有两个方法parkunpark 这两个就像是wait和notify/notifyAll,但是又不相同,可以暂时理解为就是暂停线程和启动线程

详细的介绍可以看看这个博客 : https://www.jianshu.com/p/da76b6ab56be

关于如何使用ReentrantLock就不在赘述了直接开始看代码,本来是想把类的关系图放这的,但是我的idea好像有点问题,你们可以自己打开idea看,ctrl+alt+u打开类的关系图

public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock(true);
lock.lock();
lock.unlock();
}

构造方法

public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

lock方法

public void lock() {
sync.lock();
}

点到里面实际调用的是FairSync类中的lock()方法

final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

tryAcuqire方法

protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

首先是获取了当前的线程,之后有个getState,这个方法返回的是当前这个锁的状态

protected final int getState() {
return state;
}

hasQueuedPredecessors

先来看Node,这个Node是一个组成双向链表的实体类,几个重要的属性

volatile int waitStatus;

volatile Node prev;

volatile Node next;

volatile Thread thread;

Node nextWaiter;

waitStatus 存放当前结点的状态

prev 存放上个结点

next 存放下个结点

thread 存放线程

nextWaiter 翻译是下个服务员,我理解是为下个节点服务,后面咱们细说

public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

这里有两个属性,tail尾结点,head头结点,之后下面一个判断分别是

头结点不等于尾结点 并且 (头结点的下一结点不等于null或者头结点后面一个结点的线程不等于当前线程)

if (c == 0) {
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}

hasQueuedPredecessors()接着就是一个cas,修改的这个锁的状态

如果成功,则调用setExclusiveOwnerThread()

protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}

将当前线程存放到exclusiveOwnerThread属性中

那么在没有冲突的情况下lock的方法就走完了,现在咱们假设只有一个线程,从头来捋一下加锁的过程

试跑一下

咱们顺着逻辑捋一下,从最开始的lock()方法开始,前面的就不写了,直接到acquire

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

进入acquire

protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}

因为这个getState()方法获取的是属性state 而这个属性又没有其他的赋值操作,初始化就是0,进入if(c==0)

之后进入hasQueuedPredecessors()

public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

首先第一个判断就已经是false了,因为tail和head都没有进行过初始化,都是null,所以等于,直接返回 false,而在hasQueuedPredecessors()方法前面还有一个!取反为true,直接进入if代码块

设置完exclusiveOwnerThread属性后就return true,走出lock()方法,加锁方法结束

exclusiveOwnerThread属性存放的是当前那个线程在占有锁

这是在没有线程获取锁冲突的情况下,如果现在两个线程同时来的话,还是看tryAcquire方法

protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

咱们现在假设线程A获取到锁,去执行业务代码了,线程B进入

getState()获取的值就不在是0了,因为线程A执行完compareAndSetState(0, acquires)改的就是getState()方法获取的state属性

那么进入else if (current == getExclusiveOwnerThread()) 哎这个不是获取当前占有锁的那个线程的方法吗,是的

那为什么有这个判断呢,ReentrantLock的特性 重入锁,啥叫重入锁?:同一个线程可以多次获取同一个锁 例如下面的例子

public class Test{
private static final ReentrantLock LOCK=new ReentrantLock(true); public void a(){
LOCK.lock();
b();
LOCK.unlock();
} public void b(){
LOCK.lock();
//xxxxxx
LOCK.unlock();
}
}

如果没有重入锁的特性,那么这个方法是不是就死锁了呢?,假设当我们一个线程去调用a方法时..

  • a : 兄弟我需要锁才能执行你的代码啊

  • b : 那你先解锁啊

  • a : 我要调用完你我才能解锁啊

  • b : 那你不解锁咋调用我啊

........

好了回到代码中,就是将锁持有的状态+1,设置后返回true,因为我们现在是B线程,所以这个if不成立,返回false

回到acquire方法

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

因为tryAcquire为false,取反继续执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

addWaiter

先来看里面的addWaiter方法吧,传了一个参数Node.EXCLUSIVE

static final Node EXCLUSIVE = null;

这个参数是Node类中的一个属性

private Node addWaiter(Node mode) {
//创建一个Node
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}

Node的有参构造如下

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

首先是创建了一个Node结点,之后判断如果tail结点不为null,因为A线程走完tryAcquire直接返回了,tail和head都是null,所以if不成立,进入enq方法

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;
}
}
}
}

首先还是获取tail,那么这时候还是为null,因为我们的假设就两个线程,A线程已经去执行业务了,所以进去第一个if

通过cas来设置头节点为一个new Node() 注意!这里是新new的Node,设置完后将头在设置给尾,那么此时的节点关系如下

em?? 咱们这B节点也没有添加进链表啊,别急,看看上面的for(;;)

在下次循环的时候tail还等于null吗?答案是否定的

之后还是头节点赋值给t,将B节点的上一个设置为t,cas设置tail,成功后t节点的next设置为B节点,返回t (返回的值其实没接收)

挺简单的逻辑说的太费劲了,还是看图吧,执行完第二遍for后节点关系如下

这个enq方法是百分百能确定这个节点已经添加进去的,因为你不添加进去就出不来这个方法,那么返回addWaiter方法,走完这个enq就还有一句话,return node;

acquireQueued

final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

那么一进来就定义了一个failed用来处理如果发生错误需要将链表中错误的节点移除,咱先不看

之后的一个interrupted存放是否被打断过,继续发现还是一个for(;;),第一步执行了node.predecessor()

final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}

也就是先获取了下B节点的上一个,也就是那个线程为null的空节点(注意:不是null,而是一个空的Node)

判断上个节点是不是head,如果是,则尝试获取锁,这个tryAcquire()方法就是开始的那个方法,那么这一步是什么意思呢

ReentrantLock的做法,如果必须创建链表,则head指向的Node节点一直就是一个空节点

这句话可能不太严谨,但是在链表存在的大部分时间内,head也确实指向的是一个空节点

继续看代码,假设现在A线程还是没有完成业务代码,没有执行unlock(),那么我们进入下个if,

if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())
interrupted = true;

shouldParkAfterFailedAcquire

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

代码不多,但是不太好理解 开始还是获取B节点上个节点的状态,也就是那个空节点,因为咱们一路代码跟过来的,没有看到哪里对空节点的state属性进行过修改,所以它还是0

那么第一个判断

static final int SIGNAL    = -1;  //Node类中的属性

空节点的状态是否为-1,显然不是,if(ws>0)也不会进,直接进入else,cas修改空节点的状态,改为了-1,然后返回

if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())
interrupted = true;

因为是&&阻断了后面的方法,所以不进入,那么本次循环结束,最外层是个for(;;)所以下次循环开始

我们还是假设通过tryAcquire()没有获取到锁,又进入了shouldParkAfterFailedAcquire

那么这次第一个if我们就会进去,因为上次循环已经将B节点前面的一个空节点的状态改为-1了,return true

回到if那么就进入parkAndCheckInterrupt方法

private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}

park,那么B线程就停在这里了,把目光回到A线程,它终于执行完业务代码了,执行unlock

unlock

public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

看第一个if中的tryRelease

protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

第一件事就是将锁的状态-1,因为它重入一次就+1,这也是为什么调用lock多少次就需要调用unlock多少次,因为要保证锁的状态为0

之后判断加锁的线程和解锁的线程是不是同一个,不是抛出异常

boolean free = false;这个来标识这个锁是不是已经没有人持有了,因为A线程就调用了一次lock,所以if(c==0)成立

将free 改为true后将当前持有锁的线程设置为null,设置锁的状态,返回true,回到release方法

因为返回true,所以进入if,判断head节点不为空,并且头节点的状态不为0

那么头节点是空的吗? -不是 因为B节点在初始化链表是添加了一个空的节点(再说一遍不是null!是空的Node节点)

那么头节点的状态是0吗? -不是 我们在第二次执行shouldParkAfterFailedAcquire()方法时已经将头节点的状态设置为-1了

那么进入unparkSuccessor()

    private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}

获取,cas将头节点的状态赋值为0,获取头节点的下一个节点,也就是我们的B节点,那么if (s == null || s.waitStatus > 0) 为false,走最下面的if(s!=null)

就一句话 unpark(s.thread)

到此,AB线程都走完了

篇幅有点长了,这两天再接着写,拜拜

ReentrantLock 公平锁源码 第0篇的更多相关文章

  1. ReentrantLock之非公平锁源码分析

    本文分析的ReentrantLock所对应的Java版本为JDK8. 在阅读本文前,读者应该知道什么是CAS.自旋. 由于ReentrantLock的公平锁和非公平锁中有许多共同代码,本文只会对这两种 ...

  2. ReentrantLock 的公平锁源码分析

    ReentrantLock 源码分析   以公平锁源码解析为例: 1:数据结构: 维护Sync 对象的引用:   private final Sync sync; Sync对象继承 AQS,  Syn ...

  3. ReentrantLock之公平锁源码分析

    本文分析的ReentrantLock所对应的Java版本为JDK8. 在阅读本文前,读者应该知道什么是CAS.自旋. 本文大纲 1.ReentrantLock公平锁简介 2.AQS 3.lock方法 ...

  4. ReentrantLock与synchronized 源码解析

    一.概念及执行原理   在 JDK 1.5 之前共享对象的协调机制只有 synchronized 和 volatile,在 JDK 1.5 中增加了新的机制 ReentrantLock,该机制的诞生并 ...

  5. Java关于ReentrantLock获取锁和释放锁源码跟踪

    通过对ReentrantLock获取锁和释放锁源码跟踪主要想进一步深入学习AQS. 备注:AQS中的waitStatus状态码含义:

  6. ReentrantLock锁 源码分析

    根据下面代码分析下ReentrantLock 获得锁和释放锁的过程 ReentrantLock lock = new ReentrantLock(); lock.lock();//获得锁 lock.u ...

  7. ReentrantLock 与 AQS 源码分析

    ReentrantLock 与 AQS 源码分析 1. 基本结构    重入锁 ReetrantLock,JDK 1.5新增的类,作用与synchronized关键字相当,但比synchronized ...

  8. ReentrantLock和condition源码浅析(二)

    转载请注明出处... 接着上一篇的ReentrantLock和condition源码浅析(一),这篇围绕着condition 一.condition的介绍 在这里为了作对比,引入Object类的两个方 ...

  9. JUC源码分析-集合篇(十)LinkedTransferQueue

    JUC源码分析-集合篇(十)LinkedTransferQueue LinkedTransferQueue(LTQ) 相比 BlockingQueue 更进一步,生产者会一直阻塞直到所添加到队列的元素 ...

  10. JUC源码分析-集合篇(九)SynchronousQueue

    JUC源码分析-集合篇(九)SynchronousQueue SynchronousQueue 是一个同步阻塞队列,它的每个插入操作都要等待其他线程相应的移除操作,反之亦然.SynchronousQu ...

随机推荐

  1. JVM之PC寄存器(Program Counter Register)

    基本特性: 当前线程执行的字节码的行号指示器. Java虚拟机支持多个线程同时执行,每一个线程都有自己的pc寄存器. 任意时刻,一个线程都只会执行一个方法的代码,称为该线程的当前方法,对于非nativ ...

  2. GitHUb 代码提交遇到的问题以及解决办法

    git 添加代码出现以下错误: fatal: Unable to create 'F:/wamp/www/ThinkPhpStudy/.git/index.lock': File exists. If ...

  3. spring源码分析之spring-jms模块详解

    0 概述 spring提供了一个jms集成框架,这个框架如spring 集成jdbc api一样,简化了jms api的使用. jms可以简单的分成两个功能区,消息的生产和消息的消费.JmsTempl ...

  4. Sharepoint网站创建自定义导航全记录

    转:http://tech.it168.com/a2009/1207/820/000000820524_all.shtml [IT168 技术文档]在一个Sharepoint网站中可以创建子网站,页面 ...

  5. poj 1008

    #include<iostream>#include<string> using namespace std;string hname[19] = { "pop&qu ...

  6. (转)每天一个linux命令(27):linux chmod命令

    场景:在项目部署过程中经常需要给不同目录授权! 1 简介 chmod命令用于改变linux系统文件或目录的访问权限.用它控制文件或目录的访问权限.该命令有两种用法.一种是包含字母和操作符表达式的文字设 ...

  7. 比较三个 CSS 预处理器:Sass、LESS 和 Stylus(下)

    五.Mixins (混入) Mixins 有点像是函数或者是宏,当你某段 CSS 经常需要在多个元素中使用时,你可以为这些共用的 CSS 定义一个 Mixin,然后你只需要在需要引用这些 CSS 地方 ...

  8. openstack-ocata-镜像服务3

    一. 镜像服务概述 镜像服务(glance)使用户能够发现.登记,并检索虚拟机镜像.它提供了一个REST API,使您可以查询虚拟机镜像元数据和检索一个实际的形象.可以存储虚拟机镜像通过镜像服务在不同 ...

  9. ansible的tags

    执行ansible-playbook时可以使用--tags "tag1,tag2..." 或者 --skip-tags "tag1,tag2..."指定执行的t ...

  10. 解决log4j和self4j日志报错Could NOT find resource [logback.groovy]及Could NOT find resource [logback-test.xml]问题

    事件背景: 我的log4j和self4j按照网上的配置,配置成功了,但是报错如下: 让我很是郁闷,于是找了一大圈........ 解决方案: 总结来说就是:log4j.properties和logba ...