使用举例

默认是非公平锁,公平锁构造参数用true

package com.study.demo;import java.util.concurrent.locks.ReentrantLock;/*** Created by Administrator on 2019\3\2 0002.*/
public class Test {private static ReentrantLock lock = new ReentrantLock();private static int count;public static void main(String[] args) {for (int i = 0; i < 2; i++) {new Thread(new Runnable() {@Overridepublic void run() {try {lock.lock();count++;Thread.sleep(10000000);} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}}).start();}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}try {lock.lock();count++;Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}
}

构造函数

    private final Sync sync;public ReentrantLock() {sync = new NonfairSync();}

AbstractQueuedSynchronizer

简称AQS,为构建不同的同步组件(重入锁,读写锁,CountDownLatch等)提供了可扩展的基础框架
AQS中同步等待队列的实现是一个带头尾指针(这里用指针表示引用是为了后面讲解源码时可以更直观形象,况且引用本身是一种受限的指针)且不带哨兵结点(后文中的头结点表示队列首元素结点,不是指哨兵结点)的双向链表

/*** Head of the wait queue, lazily initialized.  Except for* initialization, it is modified only via method setHead.  Note:* If head exists, its waitStatus is guaranteed not to be* CANCELLED.*/
private transient volatile Node head;//指向队列首元素的头指针/*** Tail of the wait queue, lazily initialized.  Modified only via* method enq to add new wait node.*/
private transient volatile Node tail;//指向队列尾元素的尾指针

Node节点,同步等待队列的节点
waitStatus:对于重入锁而言,主要有3个值。0:初始化状态;-1(SIGNAL):当前结点表示的线程在释放锁后需要唤醒后续节点的线程;1(CANCELLED):在同步队列中等待的线程等待超时或者被中断,取消继续等待。

    static final class Node {/** Marker to indicate a node is waiting in shared mode */static final Node SHARED = new Node();/** Marker to indicate a node is waiting in exclusive mode */static final Node EXCLUSIVE = null;/** waitStatus value to indicate thread has cancelled */static final int CANCELLED =  1;/** waitStatus value to indicate successor's thread needs unparking */static final int SIGNAL    = -1;/** waitStatus value to indicate thread is waiting on condition */static final int CONDITION = -2;/*** waitStatus value to indicate the next acquireShared should* unconditionally propagate*/static final int PROPAGATE = -3;/*** Status field, taking on only the values:*   SIGNAL:     The successor of this node is (or will soon be)*               blocked (via park), so the current node must*               unpark its successor when it releases or*               cancels. To avoid races, acquire methods must*               first indicate they need a signal,*               then retry the atomic acquire, and then,*               on failure, block.*   CANCELLED:  This node is cancelled due to timeout or interrupt.*               Nodes never leave this state. In particular,*               a thread with cancelled node never again blocks.*   CONDITION:  This node is currently on a condition queue.*               It will not be used as a sync queue node*               until transferred, at which time the status*               will be set to 0. (Use of this value here has*               nothing to do with the other uses of the*               field, but simplifies mechanics.)*   PROPAGATE:  A releaseShared should be propagated to other*               nodes. This is set (for head node only) in*               doReleaseShared to ensure propagation*               continues, even if other operations have*               since intervened.*   0:          None of the above** The values are arranged numerically to simplify use.* Non-negative values mean that a node doesn't need to* signal. So, most code doesn't need to check for particular* values, just for sign.** The field is initialized to 0 for normal sync nodes, and* CONDITION for condition nodes.  It is modified using CAS* (or when possible, unconditional volatile writes).*/volatile int waitStatus;/*** Link to predecessor node that current node/thread relies on* for checking waitStatus. Assigned during enqueuing, and nulled* out (for sake of GC) only upon dequeuing.  Also, upon* cancellation of a predecessor, we short-circuit while* finding a non-cancelled one, which will always exist* because the head node is never cancelled: A node becomes* head only as a result of successful acquire. A* cancelled thread never succeeds in acquiring, and a thread only* cancels itself, not any other node.*/volatile Node prev;/*** Link to the successor node that the current node/thread* unparks upon release. Assigned during enqueuing, adjusted* when bypassing cancelled predecessors, and nulled out (for* sake of GC) when dequeued.  The enq operation does not* assign next field of a predecessor until after attachment,* so seeing a null next field does not necessarily mean that* node is at end of queue. However, if a next field appears* to be null, we can scan prev's from the tail to* double-check.  The next field of cancelled nodes is set to* point to the node itself instead of null, to make life* easier for isOnSyncQueue.*/volatile Node next;/*** The thread that enqueued this node.  Initialized on* construction and nulled out after use.*/volatile Thread thread;/*** Link to next node waiting on condition, or the special* value SHARED.  Because condition queues are accessed only* when holding in exclusive mode, we just need a simple* linked queue to hold nodes while they are waiting on* conditions. They are then transferred to the queue to* re-acquire. And because conditions can only be exclusive,* we save a field by using special value to indicate shared* mode.*/Node nextWaiter;

讲解:
1.同步队列是个先进先出(FIFO)队列,获取锁失败的线程将构造结点并加入队列的尾部,并阻塞自己。
2.队列首结点可以用来表示当前正获取锁的线程。
3.当前线程释放锁后将尝试唤醒后续处结点中处于阻塞状态的线程。

同步状态变量
AbstractQueuedSynchronizer

/*** The synchronization state.*/
private volatile int state;

这是一个带volatile前缀的int值,是一个类似计数器的东西。在不同的同步组件中有不同的含义。以ReentrantLock为例,state可以用来表示该锁被线程重入的次数。当state为0表示该锁不被任何线程持有;当state为1表示线程恰好持有该锁1次(未重入);当state大于1则表示锁被线程重入state次。因为这是一个会被并发访问的量,为了防止出现可见性问题要用volatile进行修饰。

持有同步状态的线程标志
AbstractOwnableSynchronizer

/*** The current owner of exclusive mode synchronization.*/
private transient Thread exclusiveOwnerThread;

这是在独占同步模式下标记持有同步状态线程的。ReentrantLock就是典型的独占同步模式,该变量用来标识锁被哪个线程持有。

非公平模式加锁

ReentrantLock

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

ReentrantLock.NonfairSync

final void lock() {//以cas方式尝试将AQS中的state从0更新为1if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());//获取锁成功则将当前线程标记为持有锁的线程,然后直接返回elseacquire(1);//获取锁失败则执行该方法
}

AbstractQueuedSynchronizer

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

ReentrantLock.NonfairSync

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

ReentrantLock.Sync

/*** 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();//获取state变量的值,即当前锁被重入的次数if (c == 0) {   //state为0,说明当前锁未被任何线程持有if (compareAndSetState(0, acquires)) { //以cas方式获取锁setExclusiveOwnerThread(current);  //将当前线程标记为持有锁的线程return true;//获取锁成功,非重入}}else if (current == getExclusiveOwnerThread()) { //当前线程就是持有锁的线程,说明该锁被重入了int nextc = c + acquires;//计算state变量要更新的值if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);//非同步方式更新state值return true;  //获取锁成功,重入}return false;     //走到这里说明尝试获取锁失败
}

这是非公平模式下获取锁的通用方法。它囊括了当前线程在尝试获取锁时的所有可能情况:

1.当前锁未被任何线程持有(state=0),则以cas方式获取锁,若获取成功则设置exclusiveOwnerThread为当前线程,然后返回成功的结果;若cas失败,说明在得到state=0和cas获取锁之间有其他线程已经获取了锁,返回失败结果。
2.若锁已经被当前线程获取(state>0,exclusiveOwnerThread为当前线程),则将锁的重入次数加1(state+1),然后返回成功结果。因为该线程之前已经获得了锁,所以这个累加操作不用同步。
3.若当前锁已经被其他线程持有(state>0,exclusiveOwnerThread不为当前线程),则直接返回失败结果

AbstractQueuedSynchronizer

    private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}/*** Inserts node into queue, initializing if necessary. See picture above.* @param node the node to insert* @return node's predecessor*/
private Node enq(final Node node) {for (;;) {Node t = tail;//t指向当前队列的最后一个节点,队列为空则为nullif (t == null) { // Must initialize  //队列为空if (compareAndSetHead(new Node())) //构造新结点,CAS方式设置为队列首元素,当head==null时更新成功tail = head;//尾指针指向首结点} else {  //队列不为空node.prev = t;if (compareAndSetTail(t, node)) { //CAS将尾指针指向当前结点,当t(原来的尾指针)==tail(当前真实的尾指针)时执行成功t.next = node;    //原尾结点的next指针指向当前结点return t;}}}
}

外层的for循环保证了所有获取锁失败的线程经过失败重试后最后都能加入同步队列。因为AQS的同步队列是不带哨兵结点的,故当队列为空时要进行特殊处理,这部分在if分句中。注意当前线程所在的结点不能直接插入空队列,因为阻塞的线程是由前驱结点进行唤醒的。故先要插入一个结点作为队列首元素,当锁释放时由它来唤醒后面被阻塞的线程,从逻辑上这个队列首元素也可以表示当前正获取锁的线程,虽然并不一定真实持有其线程实例。

AbstractQueuedSynchronizer

final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;//死循环,正常情况下线程只有获得锁才能跳出循环for (;;) {final Node p = node.predecessor();//获得当前线程所在结点的前驱结点//第一个if分句if (p == head && tryAcquire(arg)) { setHead(node); //将当前结点设置为队列头结点p.next = null; // help GCfailed = false;return interrupted;//正常情况下死循环唯一的出口}//第二个if分句if (shouldParkAfterFailedAcquire(p, node) &&  //判断是否要阻塞当前线程parkAndCheckInterrupt())      //阻塞当前线程interrupted = true;}} finally {if (failed)cancelAcquire(node);}
}

AbstractQueuedSynchronizer

/*** Checks and updates status for a node that failed to acquire.* Returns true if thread should block. This is the main signal* control in all acquire loops.  Requires that pred == node.prev.** @param pred node's predecessor holding status* @param node the node* @return {@code true} if thread should block*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL) //状态为SIGNAL/** This node has already set status asking a release* to signal it, so it can safely park.*/return true;if (ws > 0) { //状态为CANCELLED,/** Predecessor was cancelled. Skip over predecessors and* indicate retry.*/do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else { //状态为初始化状态(ReentrentLock语境下)/** 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.*/compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;
}

针对前驱结点pred的状态会进行不同的处理
1.pred状态为SIGNAL,则返回true,表示要阻塞当前线程。
2.pred状态为CANCELLED,则一直往队列头部回溯直到找到一个状态不为CANCELLED的结点,将当前节点node挂在这个结点的后面。
3.pred的状态为初始化状态,此时通过compareAndSetWaitStatus(pred, ws, Node.SIGNAL)方法将pred的状态改为SIGNAL。
确保当前结点的前驱结点的状态为SIGNAL,SIGNAL意味着线程释放锁后会唤醒后面阻塞的线程。毕竟,只有确保能够被唤醒,当前线程才能放心的阻塞。

其他两种情况则因为返回false而重新执行一遍
for循环。这种延迟阻塞其实也是一种高并发场景下的优化,试想我如果在重新执行循环的时候成功获取了锁,是不是线程阻塞唤醒的开销就省了呢?
AbstractQueuedSynchronizer

/*** Convenience method to park and then check if interrupted** @return {@code true} if interrupted*/
private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();
}

调用LockSupport的park方法来阻塞当前线程

线程从加入同步队列到成功获取锁的过程

AbstractQueuedSynchronizer

    /*** Convenience method to interrupt current thread.*/static void selfInterrupt() {Thread.currentThread().interrupt();}

非公平模式下的加锁流程

非公平模式解锁流程

ReentrantLock

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

AbstractQueuedSynchronizer

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

ReentrantLock.Sync

protected final boolean tryRelease(int releases) {int c = getState() - releases; //计算待更新的state值if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) { //待更新的state值为0,说明持有锁的线程未重入,一旦释放锁其他线程将能获取free = true; setExclusiveOwnerThread(null);//清除锁的持有线程标记}setState(c);//更新state值return free;
}

AbstractQueuedSynchronizer

   private void unparkSuccessor(Node node) {/** If status is negative (i.e., possibly needing signal) try* to clear in anticipation of signalling.  It is OK if this* fails or if status is changed by waiting thread.*/int ws = node.waitStatus;if (ws < 0)compareAndSetWaitStatus(node, ws, 0);/** Thread to unpark is held in successor, which is normally* just the next node.  But if cancelled or apparently null,* traverse backwards from tail to find the actual* non-cancelled successor.*/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);}

解锁流程

公平锁相比非公平锁的不同

加锁不同,解锁相同
ReentrantLock.FairSync

    static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}

AbstractQueuedSynchronizer

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

tryAcquire方法不同,其他三个方法acquireQueued和addWaiter,selfInterrupt都相同

ReentrantLock.FairSync

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

AbstractQueuedSynchronizer

    public final boolean hasQueuedPredecessors() {// The correctness of this depends on head being initialized// before tail and on head.next being accurate if the current// thread is first in queue.Node t = tail; // Read fields in reverse initialization orderNode h = head;Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());}

问题

为什么基于FIFO的同步队列可以实现非公平锁?
非公平锁的加锁流程,线程在进入同步队列等待之前有两次抢占锁的机会:

第一次是非重入式的获取锁,只有在当前锁未被任何线程占有(包括自身)时才能成功;
第二次是在进入同步队列前,包含所有情况的获取锁的方式。
只有这两次获取锁都失败后,线程才会构造结点并加入同步队列等待。

为什么非公平锁性能好?
线程不必加入等待队列就可以获得锁,不仅免去了构造结点并加入队列的繁琐操作,同时也节省了线程阻塞唤醒的开销,线程阻塞和唤醒涉及到线程上下文的切换和操作系统的系统调用,是非常耗时的。在高并发情况下,如果线程持有锁的时间非常短,短到线程入队阻塞的过程超过线程持有并释放锁的时间开销,那么这种抢占式特性对并发性能的提升会更加明显。

ReentrantLock1.8源码相关推荐

  1. 查看Hotspot源码,查看java各个版本源码的网站,如何查看jdk源码

    java开发必知必会之看源码,而看源码的第一步则是找到源码

  2. 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析

    目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...

  3. liunx上mysql源码安装mysql,搞定linux上MySQL编程(一):linux上源码安装MySQL

    [版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 1. 首先下载源码包: ftp://ftp.jaist.ac.jp/pub/m ...

  4. java调用clang编译的so_写Java这么久,JDK源码编译过没?编译JDK源码踩坑纪实

    好奇害死羊 很多小伙伴们做Java开发,天天写Java代码,肯定离不开Java基础环境:JDK,毕竟我们写好的Java代码也是跑在JVM虚拟机上. 一般来说,我们学Java之前,第一步就是安装JDK环 ...

  5. Go 源码里的这些 //go: 指令,go:linkname 你知道吗?

    原文地址: Go 源码里的这些 //go: 指令,你知道吗? 一文解惑 //go:linkname 指令

  6. 超详细中文预训练模型ERNIE使用指南-源码

    作者 | 高开远,上海交通大学,自然语言处理研究方向 最近在工作上处理的都是中文语料,也尝试了一些最近放出来的预训练模型(ERNIE,BERT-CHINESE,WWM-BERT-CHINESE),比对 ...

  7. 谷歌BERT预训练源码解析(二):模型构建

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_39470744/arti ...

  8. pytorch源码解析:Python层 pytorchmodule源码

    尝试使用了pytorch,相比其他深度学习框架,pytorch显得简洁易懂.花时间读了部分源码,主要结合简单例子带着问题阅读,不涉及源码中C拓展库的实现. 一个简单例子 实现单层softmax二分类, ...

  9. Bert系列(二)——源码解读之模型主体

    本篇文章主要是解读模型主体代码modeling.py.在阅读这篇文章之前希望读者们对bert的相关理论有一定的了解,尤其是transformer的结构原理,网上的资料很多,本文内容对原理部分就不做过多 ...

最新文章

  1. KubeNode:阿里巴巴云原生 容器基础设施运维实践
  2. 微软OTech第二次会议(广州站)
  3. Hadoop背景、模块介绍、架构
  4. 苹果macOS Monterey将支持一键恢复出厂设置,无需重装系统
  5. 4698. [SDOI2008]Sandy的卡片【后缀数组】
  6. linux 下录音软件,linux下录音软件Audacity[zt]
  7. C++类的定义和声明怎么写
  8. 解读|Cocos新平台、新生态的行业意义
  9. 美国J1签证可以免签去哪些国家?
  10. 示波器基础知识100问汇总
  11. OWASP A4 使用已知漏洞的插件
  12. HDU 4416 Good Article Good sentence(后缀数组)
  13. 《ASP.NET程序设计教程》目录
  14. Java 两种zero-copy零拷贝技术mmap和sendfile的介绍
  15. H3C无线AC+AP配置1—无密码登录
  16. 18岁编码少年的2021年终总结「18岁成年亦乘风 」
  17. js替换常见特殊字符
  18. 聚水潭完成3亿元B3轮融资,红杉资本中国基金独家投资...
  19. 反恐精英ol永恒python不能拍卖_《反恐精英Online》五一假期 欢乐拍卖会惊喜来袭...
  20. ghost无法备份linux系统盘,用Ghost备份Linux系统的方法

热门文章

  1. 前端加密的常见场景和方法
  2. 夫妻俩70岁积蓄70万元,俩人退休金8000元,还需要理财吗?
  3. 孩子有心理问题不愿意做心理咨询,父母该怎么办?
  4. 社交电商为什么这么火
  5. 今天有个微信好友咨询我
  6. 我有十万块,想自己创业,是做电商还是做实体店?
  7. 一个人如果控制不住自己乱消费,等同于废物
  8. 为什么很多人不跑滴滴了?
  9. go详解strings包
  10. 计算机网络————P2 标准化工作及相关组织