下图是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. CentOS6.5安装Hive-1.2.2

    注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6627669615377908231/ Hadoop环境已安装完成<CentOS6.5下安装Hadoop-2. ...

  2. Web开发之request

    request常用方法 //常用方法 //得到的是:协议+服务器地址+端口号+工程名称+资源地址+参数 String url = request.getRequestURL(); //得到的是:工程名 ...

  3. [硬件]USB TYPE C引脚定义

  4. Windows和Linux(Centos7)下安装Nginx

    安装Nginx 这篇记录只不过做了一个简单总结,如果对这块没什么概念的话可以看一下知乎的这篇文章 https://zhuanlan.zhihu.com/p/83890573 window下安装 win ...

  5. Vue框架怎么使用mediainfo.js来检测视频文件是否有音轨?

    啥是MediaInfo? 简而言之,MediaInfo是一个能读取音频和视频文件并分析和输出音视频信息的工具,能输出的内容包括视频信息,音轨,字幕等. MediaInfo也可以在web端使用,需要使用 ...

  6. html+css+js(登录页)

    直接上代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...

  7. 数据库查询语句遇到:Unknown column 'XXXX' in 'where clause'解决方法

    数据库查询语句遇到:Unknown colunm 'XXX' in 'where clause'解决方法 根本原因:可能是sql语句所用到的数据类型错误(int与String)弄错- 我的情况: 在网 ...

  8. Kubernetes的Controller进阶(十二)

    一.Controller 既然学习了Pod进阶,对于管理Pod的Controller肯定也要进阶一下,之前我们已经学习过的Controller有RC.RS和Deployment,除此之外还有吗,如果感 ...

  9. 如何快速写出高质量的 Go 代码?

    前言 团队协作开发中,必然存在着不同的代码风格,并且诸如 http body close,unhandled error 等低级错误不能完全避免.通过使用 ci lint 能够及早的发现并修复问题,提 ...

  10. host文件以及host的作用

    什么是HOST文件:Hosts是一个没有扩展名的系统文件,其基本作用就是将一些常用的网址域名与其对应的IP地址建立一个关联"数据库",当用户在浏览器中输入一个需要登录的网址时,系统 ...