下图是Lock接口清单,定义了一些抽象的锁操作。Java本身提供了内部锁机制,那么还需要显示Lock,何用?与内部加锁机制不同,Lock提供了无条件、可轮询、定时、可中断的锁获取操作;所有加锁和解锁的操作都是显式的。既然是锁,那么Lock的实现必须提供具有与内部加锁相同内存可见性语义,同时语义、调度算法、顺序保证、性能特性这些可以不同——即功能可以更强大,但是基本核心基本功能不能丢失。

    

  ReentrantLock实现了Lock接口,那么看看这个接口实现是如何在保证内部锁功能同时显式调用的。通常调用范式如下:

    

  譬如CyclicBarrier中的实现:

    

  阅读ReentrantLock源码可知其实现基于AQS(参考Java并发基础之AbstractQueuedSynchronizer(AQS) - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)):

    

  ReentrantLock只支持独占的获取操作,因此它实现了AQS中的tryAcquire/tryRelease和isHeldExclusively:

    

  下面通过具体源码分析ReentrantLock实现:

  1、公平与非公平lock()的差异:  

    ReentrantLock使用同步状态持有锁获取操作的计数,同时还维护了一个owner变量来持有当前拥有的线程标识符。只有在当期线程刚刚获取到锁,或者刚刚释放了锁的时候,才会修改owner。在tryRelease中,检查owner域以确保当期线程在执行一个unlock操作之前已经拥有了锁;在tryAcquire中,使用这个域来区分重进入的获取操作尝试与竞争的获取操作尝试:

    

  通过源码比较可知,公平锁增加了判断是否有等待线程排队这一条件:

    当一个线程A尝试去获取锁时tryAcquire会首先请求锁的状态,state 初始化为 0,表示未锁定状态;A 线程lock()时会调用 tryAcquire()独占该锁并将 state+1,如果锁未被占有尝试更新锁的状态,表明锁已被占有。因为状态可能在被观察后的几条指令中被修改了,所以tryAcquire使用compareAndSetState来尝试原子地更新状态表明这个锁现在已经被占有,并确保状态自最后一次观察后没有被修改过。此后,其他线程再 tryAcquire()时就会失败,直到 A 线程 unlock(到 state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前, A 线程自己是可以重复获取此锁的(state 会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证 state 是能回到零态的。

    假设锁状态表明它已经被占有,如果当前线程是锁的拥有者,那么获取计数会递减;如果当前线程不是锁的拥有者,那么获取操作的尝试会失败。

  2、阻塞队列与唤醒机制:

    此处为锁的最为关键的部分,即acquireQueued(...)方法内部如何实现。这部分内容AQ已经实现,如下:

            

    addWaiter(...)方法创建节点,尝试将节点追加到队列尾部。获取tail节点,将tail节点的next设置为当前节点。如果tail不存在,就初始化队列。

    在addWaiter(...)方法把Thread对象加入阻塞队列之后的工作就要靠acquireQueued(...)方法完成。线程一旦进入acquireQueued(...)就会被无限期阻塞,即使有其他线程调用interrupt()方法也不能将其唤醒,除非有其他线程释放了锁,并且该线程拿到了锁,才会从accquireQueued(...)返回。    

    那么阻塞方法在哪里呢?

      

    线程调用park()方法,自己把自己阻塞起来直到被其他线程唤醒,该方法返回。park()方法返回有两种情况:

      1) 其他线程调用了unpark(Thread t)。

      2)其他线程调用了t.interrupt()。这里要注意的是,lock()不能响应中断,但LockSupport.park()会响应中断。

    也正因为LockSupport.park()可能被中断唤醒,acquireQueued(...)方法才写了一个for死循环。唤醒之后,如果发现自己排在队列头部,就去拿锁;如果拿不到锁,则再次自己阻塞自己。不断重复此过程,直到拿到锁。

    被唤醒之后,通过Thread.interrupted()来判断是否被中断唤醒。如果是情况1,会返回false;如果是情况2,则返回true。

    需要注意的是acquireQueued方法有个返回值,这个值啥意思?虽然该方法不会中断响应,但它会记录被阻塞期间有没有其他线程向它发送过中断信号。如果有,则该方法会返回true;否则,返回false。

      

    当 acquireQueued(...)返回 true 时,会调用 selfInterrupt(),自己给自己发送中断信号,也就是自己把自己的中断标志位设为true。之所以要这么做,是因为自己在阻塞期间,收到其他线程中断信号没有及时响应,现在要进行补偿。这样一来,如果该线程在lock代码块内部有调用sleep()之类的阻塞方法,就可以抛出异常,响应该中断信号。
  3、unlock()实现分析
    上面两个点阐述了lock的实现,那么unlock是如何实现的呢?与lock不同释放锁无是否公平区别:

      

    实际上,release()里面就干了两件实事:tryRelease(...)方法释放锁;unparkSuccessor(...)方法唤醒队列中的后继者。

    



    

 

未完,待续……

    

  

显式锁之ReentrantLock实现的更多相关文章

  1. 4.显式锁 Lock

    4.1 概念 内置锁 vs 显示锁 synchronize是java语言层面实现的锁,称为内置锁.使用方便代码简洁,而且在jdk新版本优化后,性能也得到了很大的提高.synchronize是一个可重入 ...

  2. 深入理解java内置锁(synchronized)和显式锁(ReentrantLock)

    多线程编程中,当代码需要同步时我们会用到锁.Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两种同步方式.显式锁是JDK1.5引入的,这两种锁有什么异同呢? ...

  3. 深刨显式锁ReentrantLock原理及其与内置锁的区别,以及读写锁ReentrantReadWriteLock使用场景

    13.显示锁 在Java5.0之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile.Java5.0增加了一种新的机制:ReentrantLock.与之前提到过的机 ...

  4. Java并发编程之显式锁机制

    我们之前介绍过synchronized关键字实现程序的原子性操作,它的内部也是一种加锁和解锁机制,是一种声明式的编程方式,我们只需要对方法或者代码块进行声明,Java内部帮我们在调用方法之前和结束时加 ...

  5. 深入理解Java内置锁和显式锁

    synchronized and Reentrantlock 多线程编程中,当代码需要同步时我们会用到锁.Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两 ...

  6. java 并发多线程显式锁概念简介 什么是显式锁 多线程下篇(一)

    目前对于同步,仅仅介绍了一个关键字synchronized,可以用于保证线程同步的原子性.可见性.有序性 对于synchronized关键字,对于静态方法默认是以该类的class对象作为锁,对于实例方 ...

  7. Java编程的逻辑 (71) - 显式锁

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  8. 显式锁(二)Lock接口与显示锁介绍

    一.显式锁简介    显式锁,这个叫法是相对于隐式锁synchronized而言的,加锁和解锁都要用户显式地控制.显示锁Lock是在Java5中添加到jdk的,同synchronized一样,这也是一 ...

  9. 显式锁(三)读写锁ReadWriteLock

    前言:   上一篇文章,已经很详细地介绍了 显式锁Lock 以及 其常用的实现方式- - ReetrantLock(重入锁),本文将介绍另一种显式锁 - - 读写锁ReadWriteLock.    ...

随机推荐

  1. kafka学习笔记(七)kafka的状态机模块

    概述 这一篇随笔介绍kafka的状态机模块,Kafka 源码中有很多状态机和管理器,比如之前我们学过的 Controller 通道管理器 ControllerChannelManager.处理 Con ...

  2. [论文翻译] 分布式训练 Parameter Sharding 之 Google Weight Sharding

    [论文翻译] 分布式训练 Parameter sharding 之 Google Weight Sharding 目录 [论文翻译] 分布式训练 Parameter sharding 之 Google ...

  3. Android官方文档翻译 十三 3.1Supporting Different Languages

    Supporting Different Languages 支持不同语言 This class teaches you to 这节课教给你 Create Locale Directories and ...

  4. Android官方文档翻译 九 2.2Adding Action Buttons

    Adding Action Buttons 增加动作按钮 This lesson teaches you to 这节课教给你 Specify the Actions in XML 在XML中指定动作 ...

  5. kubernetes运行应用2之DaemonSet详解

    kubernetes运行应用1之Deployment详解   查看daemonset 如下,k8s自身的 DaemonSet kube-flannel-ds和kube-proxy分别负责在每个结点上运 ...

  6. BarTender调用示例

    安装BarTender 软件后,会注册一个COM 然后在项目中添加BarTender COM 引用 BarTender模板中的条码右键属性-数据源类型-嵌入的数据-名称(比如设置为 barcode p ...

  7. 多线程-创建线程第二种方式-实现Runnable接口-细节和好处

    1 package multithread2; 2 3 /* 4 * 创建线程的第一种方法:继承Thread类 5 * 6 * 创建线程的第二种方式:实现Runnable接口 7 * 8 * 1,定义 ...

  8. 南屿 带你 走进 vue

    ### Vue > Vue是一个前端js框架,由尤雨溪开发,是个人项目 Vue近几年来特别的受关注,三年前的时候angularJS霸占前端JS框架市场很长时间,接着react框架横空出世,因为它 ...

  9. 阿里巴巴基于应用和变更的交付模式|阿里巴巴DevOps实践指南

    编者按:本文源自阿里云云效团队出品的<阿里巴巴DevOps实践指南>,扫描上方二维码或前往:https://developer.aliyun.com/topic/devops,下载完整版电 ...

  10. linux环境变量添加

    目录 一:linux环境变量加载文件 1.添加环境变量顺序 2.系统环境变量执行顺序 一:linux环境变量加载文件 1.在linux中添加环境变量怎么添加呢? 文件内 /etc/profile /e ...