下图是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. 微信小程序封装mixins方法

    在app.js中这样引入 import '@src/utils/mixins' mixins函数如下 /** * 封装类似vue的混入功能 */ let native = Page Page = (o ...

  2. HIVE理论学习笔记

    概述 参加了新的公司新的工作新的环境之后,本人必须学习更多的知识,所以稳固之前的知识和学习新的知识是重中之重,新的公司把hadoop大部分的组件都进行了架构源码深度改造,所以使用过程确实遇到一些麻烦, ...

  3. Enumy:一款功能强大的Linux后渗透提权枚举工具

    Enumy是一款功能强大的Linux后渗透提权枚举工具,该工具是一个速度非常快的可移植可执行文件,广大研究人员可以在针对Linux设备的渗透测试以及CTF的后渗透阶段利用该工具实现权限提升,而Enum ...

  4. List转换Map的三种方式

    1.for循环 ... 2.使用guava Map<Long, User> maps = Maps.uniqueIndex(userList, new Function<User, ...

  5. 学习javaScript必知必会(4)~事件、事件绑定、取消事件冒泡、事件对象

    1.常用的事件: ① onload:页面加载 ② onblur: 失去焦点 onfocus: 获取焦点 ③ onclick:点击 ④ onmouseover:鼠标经过 onmouseout:鼠标离开 ...

  6. 我的MySQL学习记录 完结篇DAY05~

    课程已全部学完,Redis\mongoDB 的学习也会提上日程啦~剩下的最大的最大的问题在于练习,大量的练习,后期会发我的学习记录. 小记:也不知道是不是加强针的缘故,老是拉肚子,记录彻底感冒的一天, ...

  7. 【刷题-PAT】A1135 Is It A Red-Black Tree (30 分)

    1135 Is It A Red-Black Tree (30 分) There is a kind of balanced binary search tree named red-black tr ...

  8. 今天太开心了,因为我知道了seastar框架

    今天听说了一个新的C++语言开发的网络框架,叫做seastar. seastar有何特别之处呢?先看看官网提供的性能数据: 性能 HTTPD benchmark: cpu # request/sec ...

  9. Web播放器

    web视频播放器的使用及遇到的问题记录 TcPlayer播放器(腾讯Web超级播放器) https://cloud.tencent.com/document/product/881/20207 Ste ...

  10. 总是记不住但又总是要用的css

    有没有经常遇到一些样式每次写都要用百度呢?我收集了一些我平时经常要用到的但又总是记不住的样式.有错误的地方欢迎指正.转载请注明出处. 一.设置input 的placeholder的字体样式 input ...