本文都是根据我自己阅读源码和自己的理解记录的,仅供参考

在了解ReentrantLock原理之前,请必须要了解java的CAS,也叫compare and swap,既比较和修改,java中,在java.util.concurrent.atomic包下的基本都是实现了CAS,它们的方法命名规则基本都以compareAnd开头,例如AtomicReference类中的compareAndSet方法,不过在java中CAS的最底层实现是通过Unsafe类,而Unsafe类的compareAndSwap方法是native方法,用C++实现的,在java11中我已经找不到Unsafe这个类了,但是并不影响ReentrantLock的原理

与synchronized关键字的区别,下面这句话是doug lea和joshua bloch说的,够权威吧"在内部所不能够满足使用时,ReentranLock才被视作更高级的工具,当你需要以下特性的时候,才应该使用:可定时的,可轮询的,或者可中断的锁操作,公平队列,或者非块状结构的锁,否则,请使用synchronized"

日记:synchronized是内部锁,内置于JVM,而ReentranLock是JNI方法,C++实现,本质上依赖操作系统,言外之意就是,后续JDK优化的时候,会优化JVM,而不会优化操作系统,从JDK6开始,synchronized性能已经完全不弱于ReentranLock,强烈推荐不是特殊需求请使用synchronized关键字

第一部分:NonfairSync

首先,必须要了解一些字段,这样才能读代码

public class ReentrantLock{/***表示当前持有锁的线程,如果没有线程持有锁,则是null*当前线程持有锁之后,当前线程会修改该字段*当前线程释放锁之后,当前线程会修改该字段**/private transient Thread exclusiveOwnerThread;/***如果没有线程持有锁,则state=0,如果一个线程T1获取了锁,则T1对该值进行加1*如果T1再次获得了锁,则T1再次进行加1*当T1释放了一次锁(unlock方法),则该值减1,以此类推*也就是说:T1 lock了多少次,就必须unlock多少次,否则其他线程无法获取锁**/private volatile int state;
}
public abstract class AbstractQueuedSynchronizer{/*** Node是对线程的封装,哪个线程持有锁,那么封装这个线程的Node就是head* 也就是说:head里装的是当前持有锁的线程* 奇怪的是源码注释中没有写这个特性*/private transient volatile Node head;
}
static final class Node {// 1,说明当前节点取消了等待,比如lock.tryLock(time, unit)方法超时,以后也不会再次阻塞// -1,则说明下一个节点应该unpark或者unlock(解除挂起状态)// -2,我不清楚 TODO TODO// -3,我不清楚 TODO TODOvolatile int waitStatus;
}

1:lock()
1.initializeSyncQueue方法(如果没获取到锁会走该方法)
初始化AbstractQueuedSynchronizer中的字段head和字段tail,赋同一个值

private final void initializeSyncQueue() {Node h;if (HEAD.compareAndSet(this, null, (h = new Node())))tail = h;
}

上面的方法,在不考虑并发的情况下,等于下面的方法,这样会更好理解

private final void initializeSyncQueue() {Node h = new Node();if(this.head == null){head = h;tail = h;}
}

2.addWaiter方法(如果没获取到锁会走该方法)
如果mode为空,在双向列表添加1个节点,并返回倒数第1个(尾部)节点
如果mode不为空,在双向列表添加2个节点,返回倒数第2个节点

private Node addWaiter(Node mode) {// 创建2个新的节点,这个新的节点里保存了当前线程// 注意这个构造方法,将mode放到node的后面(node-->mode)Node node = new Node(mode);for (;;) {Node oldTail = tail;if (oldTail != null) {// 这个if里的内容是:// 将新的节点node放到尾部tail,然后再将原来的tail的next// 指向这个新节点node,说白了就是在双向链表上增加2(或者1)个节点,具体是1还是2取决于入参mode是不是空,如果是空则增加1个节点(只有node),如果不是空则增加2个节点(node和mode)node.setPrevRelaxed(oldTail);if (compareAndSetTail(oldTail, node)) {oldTail.next = node;return node;}} else {//刚进for循环先走这个,后走ifinitializeSyncQueue();}}
}

3.tryAcquire方法
这没什么说的,直接调用nonfairTryAcquire方法

protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);
}

4.nonfairTryAcquire方法
当前线程尝试获取锁,该方法是可重入的实现
如果此时没有其他线程占有锁,或者,当前占有锁的线程就是当前线程
那么就返回true,表明获取到锁
否则返回false,表示没有获取到锁

final boolean nonfairTryAcquire(int acquires) {// 获取当前线程final Thread current = Thread.currentThread();// 拿到state状态int c = getState();// 如果是0说明没有其他线程持有锁,则当前线程有机会对其赋值  if (c == 0) {// 如果赋值成功,则表示当前线程持有锁(此处采用CAS),// 对state加1,表示当前线程第一次持有锁,注意acquires=1if (compareAndSetState(0, acquires)) {// 将当前线程赋值到exclusiveOwnerThread字段// 其他地方可以通过该字段判断是哪个线程在持有锁setExclusiveOwnerThread(current);// 返回true,表示加锁成功return true;}}// 如果不是0,则说明有线程占用锁了,那么判断占有锁的线程是不是// 当前线程,如果是,则可重入(Reentran)else if (current == getExclusiveOwnerThread()) {// 因为再次持有锁(重入进来的),所以此处对state赋值// state是几,就表示当前是第几次再次获得锁int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}// 如果以上都不是,则没有获取到锁return false;
}

5.shouldParkAfterFailedAcquire方法(如果没获取到锁会走该方法)
这个方法不是很懂

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;// 如果ws=负1,表示这个节点后面的节点应该处于等待状态     if (ws == Node.SIGNAL){return true;}          if (ws > 0) {// 好遗憾,没看懂do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 好遗憾,没看懂pred.compareAndSetWaitStatus(ws, Node.SIGNAL);}return false;}

6.acquireQueued方法

final boolean acquireQueued(final Node node, int arg) {boolean interrupted = false;try {for (;;) {final Node p = node.predecessor();// 如果node是第2个节点,并且当前线程持有锁// 那么就将node变成第1个节点(head节点),原head直接GC回收掉// 通俗的说就是队列中每个节点向前挪动1个位置// NOTE:不要忘了文章开头head节点是做什么的if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCreturn interrupted;}// 如果没获取到锁,则调用LockSupport.park方法挂起,并放到队列的最后一个(tail)位置if (shouldParkAfterFailedAcquire(p, node))interrupted |= parkAndCheckInterrupt();}} catch (Throwable t) {cancelAcquire(node);if (interrupted)selfInterrupt();throw t;}}

2:unlock()
NonfairSync与FairSync的unlock是相同的

1.ReentrantLock.Sync.tryRelease(int releases)方法

// 该方法很简单,就是当前线程释放锁,state=0就表明释放成功,返回true
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;
}

2.unparkSuccessor方法

private void unparkSuccessor(Node node) {int ws = node.waitStatus;// 如果小于0,就修改成0,表示当前节点什么状态都没有,就是一个普通节点if (ws < 0)node.compareAndSetWaitStatus(ws, 0);//获取下一个节点Node s = node.next;if (s == null || s.waitStatus > 0) {s = null;// 从队列最后一个节点开始遍历,也就是说从尾向头遍历,找到waitStatus<=0并且还离头最近的节点,给s// 日记:此处我有个疑问,为啥不从头遍历?遇见<=0就跳出不就完事了吗for (Node p = tail; p != node && p != null; p = p.prev)if (p.waitStatus <= 0)s = p;}//如果没有走上面的if,则说明s的ws=1,表示取消了等待(unpark)//如果走上面的if,那么请看上面的if注释//将下一个节点解除挂起状态(unpark)if (s != null)LockSupport.unpark(s.thread);
}

第二部分:FairSync
FairSync与NonfairSync本质的不同就是在于tryAcquire方法,下面是FairSync的tryAcquire的定义,但是对于该类,我依然有些地方还是有一些疑惑的,所以此篇文章只供参考

1.AbstractQueuedSynchronizer.hasQueuedPredecessors()方法
判断当前线程所在的节点前面的节点,是否有其他等待的线程,
如果有其他等待线程,返回true
如果没有其他等待线程,返回false

public final boolean hasQueuedPredecessors() {Node h, s;// 如果头节点不为空if ((h = head) != null) {// 如果第2个节点为空,或者第2个节点不为空但是取消了等待(>0)if ((s = h.next) == null || s.waitStatus > 0) {s = null; // traverse in case of concurrent cancellation//从尾部向头部遍历,找到离头部最近的,并且waitStatus<=0的节点,用s保存这个节点for (Node p = tail; p != h && p != null; p = p.prev) {if (p.waitStatus <= 0)s = p;}}// 如果s不是空并且s中的线程不是当前线程,则说明s中保存的线程正在等待获取锁,所以返回true,表示有其他线程等待if (s != null && s.thread != Thread.currentThread())return true;}// 如果头节点为空,说明队列里没任何线程,进一步说明没有其他线程在等待,直接返回falsereturn false;
}

那么由此可以看出,FairSync与NonFairSync的本质区别,在于获取锁的时候

FairSync会绅士礼貌一点,如果已经有其他节点(或者说线程)已经在等待了,它就会默默的将当前线程放到队列的尾部

NonfairSync会自私一点,直接开始插队,能插到的话,就或得到锁,如果插队失败,在将当前线程放到队列的尾部

Java-ReentrantLock-NonfairSync/FairSync相关推荐

  1. java源码 - ReentrantLock之FairSync

    开篇  这篇文章主要是讲解FairSync公平锁的源码分析,整个内容分为加锁过程.解锁过程,CLH队列等概念.  首先一直困扰我的CLH队列的CLH的缩写我终于明白,看似三个人的人名的首字符缩写&qu ...

  2. 死磕Java——ReentrantLock

    一.死磕Java--ReentrantLock ReentrantLock是java.util.concurrent.locks包下一个可重入的默认是非公平的锁,ReentrantLock类是Lock ...

  3. java ReentrantLock 使用

    1.ReentrantLock  简单的使用 private Lock lock = new ReentrantLock(); lock.lock();用来获取锁. lock.unlock();用来释 ...

  4. java锁的概念,Java ReentrantLock锁机制概念篇

    分享Java锁机制实现原理,细节涉及volatile修饰符.CAS原子操作.park阻塞线程与unpark唤醒.双向链表.锁的公平性与非公平性.独占锁和共享锁.线程等待await.线程中断interr ...

  5. java ReentrantLock 实现原理

    使用 synchronized 来做同步处理时,锁的获取和释放都是隐式的,实现的原理是通过编译后加上不同的机器指令来实现. 而 ReentrantLock 就是一个普通的类,它是基于 AQS(Abst ...

  6. JAVA ReentrantLock 分析

    并发编程中经常用到的莫非是这个ReentrantLock这个类,线程获取锁和释放锁.还有一个则是synchronized,常用来多线程控制获取锁机制. 先写一个简单的例子. 1 2 3 4 5 6 7 ...

  7. 【java】java ReentrantLock 源码详解

    文章目录 1.概述 2.问题 3.ReentrantLock源码分析 3.1 类的继承关系 3.2 类的内部类 3.2.1 Sync类 3.2.2 NonfairSync类 3.2.3 FairSyn ...

  8. Java ReentrantLock可重入锁的源码深度解析

    Java的ReentrantLock的源码实现,包括加锁.解锁的源码,以及公平性.重入性的实现! 文章目录 1 ReentrantLock的概述 1.1 ReentrantLock的API方法 1.2 ...

  9. Java ReentrantLock源码总结 ReentrantLock源码注释翻译和解析中英文对照版 AQS虚拟类的实现

    文章目录 ReentrantLock类源码重点 具体实现 lock操作 acquire操作 tryAcquire操作 公平锁版本 非公平锁版本 lockInterruptibly操作 acquireI ...

  10. Java ReentrantLock 详解

    ReentrantLock 和 synchronized 的区别: 1.ReentrantLock 不需要代码块,在开始的时候lock,在结束的时候unlock 就可以了. 但是要注意,lock 是有 ...

最新文章

  1. 少侠!如何写一手好 SQL ?
  2. 算法 有两个相同大小数组均已按升序排列好 编程计算这两个数组的中位数 C
  3. Cocos2dx-demo演示项目:Part1
  4. 解决 Let’s Encrypt SSL 证书配置错误
  5. 无线通信AI大赛正式开放评测,50万大奖等你霸榜!
  6. 用python输入名字并打印_python的输出与输入
  7. Windows内存保护机制及绕过方法
  8. Kubernetes: 集群网络配置 - flannel
  9. 简单Android app开发_什么方法开发APP最简单?试试0代码开发平台
  10. 指数函数为例的超越函数的逼近拟合误差分析
  11. 点桌面计算机一直在加载,Win7系统在桌面点右键一直转圈的解决方法
  12. sqlserver 自定义日期函数(MMdd)
  13. 世界流调——Gary
  14. PT6303加充电电路的一套原理图
  15. 8000字干货:那些很厉害的人是怎么构建知识体系的
  16. 第十二周 静态 +友元 + 动态 + 继承 + 多文件
  17. win7 32位浏览器不能打开,64位浏览器正常的解决方法
  18. Resin 3.0.14 和 IIS6 整合
  19. 交叉编译ssh服务到ARM开发板
  20. Metrolog XG 构建坐标系

热门文章

  1. Oulipo(Hash入门第一题 Hash函数学习)
  2. MyBatis 中的mapper.xml详解
  3. django项目转pyc_Python自动化运维系列:Django搭建小博客
  4. 关于忘记MySQL的root用户密码的问题
  5. Java----日期算法(计算两个date类型的时间差)
  6. 电商库存设计mysql redis_基于redis实现的扣减库存
  7. ie 无人操作自动关闭_win7的IE自动关闭如何解决?
  8. 计算机操作员高级工试题一,计算机操作员高级工操作题复习资料
  9. 三星可折叠手机正式亮相
  10. 2018年5月14日第4课—文件隐藏权限、特殊权限、find命令等