内容包括:
  1、ReentrantLock函数分析
  2、ReentrantLock公平锁源码
-------------------------------------------
ReentrantLock是一个可重入的互斥锁,又被称为“独占锁”。
  ReentrantLock锁在同一个时间点只能被一个线程锁持有;而可重入的意思是,ReentrantLock锁,可以被单个线程多次获取。
  ReentrantLock分为“公平锁”和“非公平锁”。它们的区别体现在获取锁的机制上是否,ReentraantLock是通过一个FIFO的等待队列来管理获取该锁所有线程的。在“公平锁”的机制下,线程依次排队获取锁;而“非公平锁”在锁是可获取状态时,不管自己是不是在队列的开头都会获取锁。
一、ReentrantLock函数列表

 1 // 创建一个 ReentrantLock ,默认是“非公平锁”。
 2 ReentrantLock()
 3 // 创建策略是fair的 ReentrantLock。fair为true表示是公平锁,fair为false表示是非公平锁。
 4 ReentrantLock(boolean fair)
 5 // 查询当前线程保持此锁的次数。
 6 int getHoldCount()
 7 // 返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null。
 8 protected Thread getOwner()
 9 // 返回一个 collection,它包含可能正等待获取此锁的线程。
10 protected Collection<Thread> getQueuedThreads()
11 // 返回正等待获取此锁的线程估计数。
12 int getQueueLength()
13 // 返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程。
14 protected Collection<Thread> getWaitingThreads(Condition condition)
15 // 返回等待与此锁相关的给定条件的线程估计数。
16 int getWaitQueueLength(Condition condition)
17 // 查询给定线程是否正在等待获取此锁。
18 boolean hasQueuedThread(Thread thread)
19 // 查询是否有些线程正在等待获取此锁。
20 boolean hasQueuedThreads()
21 // 查询是否有些线程正在等待与此锁有关的给定条件。
22 boolean hasWaiters(Condition condition)
23 // 如果是“公平锁”返回true,否则返回false。
24 boolean isFair()
25 // 查询当前线程是否保持此锁。
26 boolean isHeldByCurrentThread()
27 // 查询此锁是否由任意线程保持。
28 boolean isLocked()
29 // 获取锁。
30 void lock()
31 // 如果当前线程未被中断,则获取锁。
32 void lockInterruptibly()
33 // 返回用来与此 Lock 实例一起使用的 Condition 实例。
34 Condition newCondition()
35 // 仅在调用时锁未被另一个线程保持的情况下,才获取该锁。
36 boolean tryLock()
37 // 如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
38 boolean tryLock(long timeout, TimeUnit unit)
39 // 试图释放此锁。
40 void unlock()

ReentrantLock函数列表

二、基本概念

1. AQS -- 指AbstractQueuedSynchronizer类。

AQS是java中管理“锁”的抽象类,锁的许多公共方法都是在这个类中实现。AQS是独占锁(例如,ReentrantLock)和共享锁(例如,Semaphore)的公共父类。

2. AQS锁的类别 -- 分为“独占锁”和“共享锁”两种。

(01) 独占锁 -- 锁在一个时间点只能被一个线程锁占有。根据锁的获取机制,它又划分为“公平锁”和“非公平锁”。公平锁,是按照通过CLH等待线程按照先来先得的规则,公平的获取锁;而非公平锁,则当线程要获取锁时,它会无视CLH等待队列而直接获取锁。独占锁的典型实例子是ReentrantLock,此外,ReentrantReadWriteLock.WriteLock也是独占锁。
    (02) 共享锁 -- 能被多个线程同时拥有,能被共享的锁。JUC包中的ReentrantReadWriteLock.ReadLock,CyclicBarrier, CountDownLatch和Semaphore都是共享锁。

3. CLH队列

CLH队列是AQS中“等待锁”的线程队列。在多线程中,为了保护竞争资源不被多个线程同时操作而起来错误,我们常常需要通过锁来保护这些资源。在独占锁中,竞争资源在一个时间点只能被一个线程锁访问;而其它线程则需要等待。CLH就是管理这些“等待锁”的线程的队列。
    CLH是一个非阻塞的 FIFO 队列。也就是说往里面插入或移除一个节点的时候,在并发条件下不会阻塞,而是通过自旋锁和 CAS 保证节点插入和移除的原子性。

4. CAS函数 -- Compare And Swap

CAS函数,是比较并交换函数,它是原子操作函数;即,通过CAS操作的数据都是以原子方式进行的。例如,compareAndSetHead(), compareAndSetTail(), compareAndSetNext()等函数。它们共同的特点是,这些函数所执行的动作是以原子的方式进行的。

三、JUC-公平锁-获取锁

获取锁

    public void lock() {//调用FairSync的lock方法
        sync.lock();}//继承Sync   Sync继承AbstractQueuedSynchronizer类static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {//调用AbstractQueuedSynchronizer的acquire方法acquire(1);}/*** Fair version of tryAcquire.  Don't grant access unless* recursive call or no waiters or is first.*/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;}}

lock

AQS 的acquire()实现

    public final void acquire(int arg) {//tryAcquire尝试获取锁 //addWaiter(Node.EXCLUSIVE), arg)如果失败新增等待节点//acquireQueued根据队列获取锁if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

aqs实现的acquire()

(01) “当前线程”首先通过tryAcquire()尝试获取锁。获取成功的话,直接返回;尝试失败的话,进入到等待队列排序等待(前面还有可能有需要线程在等待该锁)。
(02) “当前线程”尝试失败的情况下,先通过addWaiter(Node.EXCLUSIVE)来将“当前线程”加入到"CLH队列(非阻塞的FIFO队列)"末尾。CLH队列就是线程等待队列。
(03) 再执行完addWaiter(Node.EXCLUSIVE)之后,会调用acquireQueued()来获取锁。

FairSync.tryAcquire()

/*** Fair version of tryAcquire.  Don't grant access unless* recursive call or no waiters or is first.*/

protected final boolean tryAcquire(int acquires) {
    // 获取“当前线程”final Thread current = Thread.currentThread();// 获取“独占锁”的状态int c = getState();// c=0意味着“锁没有被任何线程锁拥有”,if (c == 0) {// 若“锁没有被任何线程锁拥有”,// 则判断“当前线程”是不是CLH队列中的第一个线程线程,// 若是的话,则获取该锁,设置锁的状态,并切设置锁的拥有者为“当前线程”。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;
}

tryAcquire()的作用就是尝试去获取锁,尝试成功的话,返回true;尝试失败的话,返回false,后续再通过其它办法来获取该锁。

hasQueuedPredecessors()

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

hasQueuedPredecessors() 是通过判断"当前线程"是不是在CLH队列的队首,来返回AQS中是不是有比“当前线程”等待更久的线程。

Node

private transient volatile Node head;    // CLH队列的队首
private transient volatile Node tail;    // CLH队列的队尾// CLH队列的节点
static final class Node {static final Node SHARED = new Node();static final Node EXCLUSIVE = null;// 线程已被取消,对应的waitStatus的值static final int CANCELLED =  1;// “当前线程的后继线程需要被unpark(唤醒)”,对应的waitStatus的值。// 一般发生情况是:当前线程的后继线程处于阻塞状态,而当前线程被release或cancel掉,因此需要唤醒当前线程的后继线程。static final int SIGNAL    = -1;// 线程(处在Condition休眠状态)在等待Condition唤醒,对应的waitStatus的值static final int CONDITION = -2;// (共享锁)其它线程获取到“共享锁”,对应的waitStatus的值static final int PROPAGATE = -3;   
// waitStatus为“CANCELLED, SIGNAL, CONDITION, PROPAGATE”时分别表示不同状态,// 若waitStatus=0,则意味着当前线程不属于上面的任何一种状态。volatile int waitStatus;// 前一节点volatile Node prev;// 后一节点volatile Node next;// 节点所对应的线程volatile Thread thread;// nextWaiter是“区别当前CLH队列是 ‘独占锁’队列 还是 ‘共享锁’队列 的标记”// 若nextWaiter=SHARED,则CLH队列是“独占锁”队列;// 若nextWaiter=EXCLUSIVE,(即nextWaiter=null),则CLH队列是“共享锁”队列。
    Node nextWaiter;// “共享锁”则返回true,“独占锁”则返回false。final boolean isShared() {return nextWaiter == SHARED;}// 返回前一节点final Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}Node() {    // Used to establish initial head or SHARED marker
    }// 构造函数。thread是节点所对应的线程,mode是用来表示thread的锁是“独占锁”还是“共享锁”。Node(Thread thread, Node mode) {     // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}// 构造函数。thread是节点所对应的线程,waitStatus是线程的等待状态。Node(Thread thread, int waitStatus) { // Used by Conditionthis.waitStatus = waitStatus;this.thread = thread;}
}

addWaiter()

private Node addWaiter(Node mode) {// 新建一个Node节点,节点对应的线程是“当前线程”,“当前线程”的锁的模型是mode。Node node = new Node(Thread.currentThread(), mode);Node pred = tail;// 若CLH队列不为空,则将“当前线程”添加到CLH队列末尾if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}// 若CLH队列为空,则调用enq()新建CLH队列,然后再将“当前线程”添加到CLH队列中。
    enq(node);return node;
}

addWaiter(Node.EXCLUSIVE)会首先创建一个Node节点,节点的类型是“独占锁”(Node.EXCLUSIVE)类型。然后,再将该节点添加到CLH队列的末尾。

final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {// interrupted表示在CLH队列的调度中,// “当前线程”在休眠时,有没有被中断过。boolean interrupted = false;for (;;) {// 获取上一个节点。// node是“当前线程”对应的节点,这里就意味着“获取上一个等待锁的线程”。final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}
}

acquireQueued()的目的是从队列中获取锁

shouldParkAfterFailedAcquire

// 返回“当前线程是否应该阻塞”
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {// 前继节点的状态int ws = pred.waitStatus;// 如果前继节点是SIGNAL状态,则意味这当前线程需要被unpark唤醒。此时,返回true。if (ws == Node.SIGNAL)return true;// 如果前继节点是“取消”状态,则设置 “当前节点”的 “当前前继节点”  为  “‘原前继节点’的前继节点”。if (ws > 0) {do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 如果前继节点为“0”或者“共享锁”状态,则设置前继节点为SIGNAL状态。
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;
}

如果前继节点状态为SIGNAL,表明当前节点需要被unpark(唤醒),此时则返回true。
如果前继节点状态为CANCELLED(ws>0),说明前继节点已经被取消,则通过先前回溯找到一个有效(非CANCELLED状态)的节点,并返回false。
如果前继节点状态为非SIGNAL、非CANCELLED,则设置前继的状态为SIGNAL,并返回false。

selfInterrupt

private static void selfInterrupt() {Thread.currentThread().interrupt();
}

如果在acquireQueued()中,当前线程被中断过,则执行selfInterrupt();否则不会执行。

在acquireQueued()中,即使是线程在阻塞状态被中断唤醒而获取到cpu执行权利;但是,如果该线程的前面还有其它等待锁的线程,根据公平性原则,该线程依然无法获取到锁。它会再次阻塞! 该线程再次阻塞,直到该线程被它的前面等待锁的线程锁唤醒;线程才会获取锁,然后“真正执行起来”!

四、JUC-公平锁-释放锁 

unlock()

public void unlock() {sync.release(1);
}

“1”的含义和“获取锁的函数acquire(1)的含义”一样,它是设置“释放锁的状态”的参数。由于“公平锁”是可重入的,所以对于同一个线程,每释放锁一次,锁的状态-1。

release()

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

release()会先调用tryRelease()来尝试释放当前线程锁持有的锁。成功的话,则唤醒后继等待线程,并返回true。否则,直接返回false。

protected final boolean tryRelease(int releases) {// c是本次释放锁之后的状态int c = getState() - releases;// 如果“当前线程”不是“锁的持有者”,则抛出异常!if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;// 如果“锁”已经被当前线程彻底释放,则设置“锁”的持有者为null,即锁是可获取状态。if (c == 0) {free = true;setExclusiveOwnerThread(null);}// 设置当前线程的锁的状态。
    setState(c);return free;
}

tryRelease()的作用是尝试释放锁。
(01) 如果“当前线程”不是“锁的持有者”,则抛出异常。
(02) 如果“当前线程”在本次释放锁操作之后,对锁的拥有状态是0(即,当前线程彻底释放该“锁”),则设置“锁”的持有者为null,即锁是可获取状态。同时,更新当前线程的锁的状态为0。

unparkSuccessor()

private void unparkSuccessor(Node node) {// 获取当前线程的状态int ws = node.waitStatus;// 如果状态<0,则设置状态=0if (ws < 0)compareAndSetWaitStatus(node, ws, 0);//获取当前节点的“有效的后继节点”,无效的话,则通过for循环进行获取。// 这里的有效,是指“后继节点对应的线程状态<=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);
}

在release()中“当前线程”释放锁成功的话,会唤醒当前线程的后继线程。
根据CLH队列的FIFO规则,“当前线程”(即已经获取锁的线程)肯定是head;如果CLH队列非空的话,则唤醒锁的下一个等待线程。

 

转载于:https://www.cnblogs.com/guoliangxie/p/6697855.html

并发编程总结4-JUC-REENTRANTLOCK-2(公平锁)相关推荐

  1. 并发编程-05线程安全性之原子性【锁之synchronized】

    文章目录 线程安全性文章索引 脑图 概述 原子性synchronized 修饰的4种对象 修饰代码块 作用范围及作用对象 Demo 多线程下 同一对象的调用 多线程下不同对象的调用 修饰方法 作用范围 ...

  2. 公平锁非公平锁的实际使用_理解ReentrantLock的公平锁和非公平锁

    学习AQS的时候,了解到AQS依赖于内部的FIFO同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成一个Node对象并将其加入到同步队列,同时会阻塞当 ...

  3. 公平锁非公平锁的实际使用_面经手册 · 第16篇《码农会锁,ReentrantLock之公平锁讲解和实现》...

    作者:小傅哥 博客:https://bugstack.cn 专题:面经手册 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 Java学多少才能找到工作? 最近经常有小伙伴问我,以为我的经验来看 ...

  4. ReentrantLock之公平锁和非公平锁详解

    ReentrantLock是一个互斥锁,它具有synchronized相同的能力:但相比之下,ReentrantLock扩展性更强,比如实现了公平锁. 下面详细拆解下ReentrantLock的公平锁 ...

  5. reentrantlock非公平锁不会随机挂起线程?_【原创】Java并发编程系列16 | 公平锁与非公平锁...

    本文为何适原创并发编程系列第 16 篇,文末有本系列文章汇总. 上一篇提到重入锁 ReentrantLock 支持两种锁,公平锁与非公平锁.那么这篇文章就来介绍一下公平锁与非公平锁. 为什么需要公平锁 ...

  6. 【JUC高并发编程】—— 初见JUC

    一.JUC 概述 什么是JUC JUC 是 Java并发编程的缩写,指的是 Java.util.concurrent 即Java工具集下的并发编程库 [说白了就是处理线程的工具包] JUC提供了一套并 ...

  7. 并发编程-19AQS同步组件之重入锁ReentrantLock、 读写锁ReentrantReadWriteLock、Condition

    文章目录 J.U.C脑图 ReentrantLock概述 ReentrantLock 常用方法 synchronized 和 ReentrantLock的比较 ReentrantLock示例 读写锁R ...

  8. 【JUC高并发编程】—— 再见JUC

    一.读写锁 读写锁概述 1️⃣ 什么是读写锁? 读写锁是一种多线程同步机制,用于在多线程环境中保护共享资源的访问 与互斥锁不同的是,读写锁允许多个线程同时读取共享资源,但在有线程请求写操作时,必须将其 ...

  9. Java并发编程(五)-- ReentrantLock

    锁是用来控制多个线程访问共享资源的方式,对共享资源加锁能够有效解决对资源的并发问题,比如在方法中或方法块中加synchronized关键字.在JDK5以后并发包中增加了Lock接口,用来实现锁功能.L ...

  10. 【并发】ReentrantLock中公平锁和非公平锁的理解

    [前言] ReentrantLock 是在JavaSE5之后,并发包中新增了Lock接口用来实现锁功能,它提供了与synchronized关键字类似的同步功能.同时ReentrantLock也是重入锁 ...

最新文章

  1. Apache运行机制剖析
  2. Spring-AOP @AspectJ进阶之命名切点
  3. 学完Python后可以做哪些工作呢?
  4. android jni 调用java对象_Android NDK开发之Jni调用Java对象
  5. react-native 显示html,react-native-webview加载本地H5
  6. stol函数在linux下使用,Linux下ATT汇编语法简介一
  7. 7-5 图形继承与多态 (50 分)
  8. win10安装时有个修复计算机,电脑开机提示自动修复怎么办?win10电脑开机提示自动修复教程...
  9. 云服务器宽带1M代表的什么意思?下载速度是1M/S吗?
  10. 概率论与数理统计--大数定律与中心极限定理
  11. 微信公众账号登陆授权开发——2
  12. linux 软件放什么目录,Linux 下的各种目录
  13. Vue 使用特殊字体
  14. 【读书笔记】理工科思维解读《万万没想到》
  15. 关于poi/Npoi创建批注后,EXCEL不能显示,wps能显示的问题
  16. python与STM32串口通讯(踩坑记录)
  17. SuperMap三维专题之倾斜摄影——倾斜摄影数据介绍篇
  18. Day10:图形用户界面和游戏开发
  19. stm32启动代码分析
  20. python音乐同步歌词_使用python扫描本地音乐并下载歌词

热门文章

  1. 好奇心机制_好奇心问题
  2. 梳理了一下前端面试必考知识点
  3. 拿下京东榜单第五首战告捷,看联想手机如何上演王者归来
  4. Linux文件和目录权限:chmod、更改所有者和所属组:chown,umask命令,隐藏权限:lsattr/chattr...
  5. PyQt5应用与实践
  6. C语言插入排序算法及代码
  7. fgetcsv()函数
  8. 表单按钮实现 type=image
  9. 一起谈.NET技术,异步调用与多线程的区别
  10. ISA Server服务器故障恢复一例系统盘符更换之后的应对方法