Java并发之ReentrantLock详解

2024-08-27 11:38

本文主要是介绍Java并发之ReentrantLock详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原文:

http://blog.csdn.net/lipeng_bigdata/article/details/52154637

     一、入题

        ReentrantLock是Java并发包中互斥锁,它有公平锁和非公平锁两种实现方式,以lock()为例,其使用方式为:

[java] view plain copy
  1. ReentrantLock takeLock = new ReentrantLock();  
  2.   
  3. // 获取锁  
  4. takeLock.lock();  
  5.   
  6. try {  
  7.     
  8.   // 业务逻辑  
  9.     
  10. finally {  
  11.   // 释放锁  
  12.   takeLock.unlock();  
  13. }  
         那么,ReentrantLock内部是如何实现锁的呢?接下来我们就以JDK1.7中的ReentrantLock的lock()为例详细研究下。


        二、ReentrantLock类的结构

        ReentrantLock类实现了Lock和java.io.Serializable接口,其内部有一个实现锁功能的关键成员变量Sync类型的sync,定义如下:

[java] view plain copy
  1. /** Synchronizer providing all implementation mechanics */  
  2. private final Sync sync;  
        而这个Sync是继承了AbstractQueuedSynchronizer的内部抽象类,主要由它负责实现锁的功能。关于AbstractQueuedSynchronizer我们会在以后详细介绍,你只要知道它内部存在一个获取锁的等待队列及其互斥锁状态下的int状态位(0当前没有线程持有该锁、n存在某线程重入锁n次)即可,该状态位也可用于其它诸如共享锁、信号量等功能。
        Sync在ReentrantLock中有两种实现类:NonfairSync、FairSync,正好对应了ReentrantLock的非公平锁、公平锁两大类型。

        

        三、获取锁主体流程

        ReentrantLock的锁功能主要是通过继承了AbstractQueuedSynchronizer的内部类Sync来实现的,其lock()获取锁的主要流程如下:

        

        首先,ReentrantLock的lock()方法会调用其内部成员变量sync的lock()方法;

        其次,sync的非公平锁NonfairSync或公平锁FairSync实现了父类AbstractQueuedSynchronizer的lock()方法,其会调用acquire()方法;

        然后,acquire()方法则在sync父类AbstractQueuedSynchronizer中实现,它只有一段代码:

[java] view plain copy
  1. public final void acquire(int arg) {  
  2.     if (!tryAcquire(arg) &&  
  3.         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  
  4.         selfInterrupt();  
  5. }  
      通过tryAcquire()方法试图获取锁,获取到直接返回结果,否则通过嵌套调用acquireQueued()、addWaiter()方法将请求获取锁的线程加入等待队列,如果成功的话,将当前请求线程阻塞,and,over!

         队列如何实现及如何添加到队列中以后再做详细分析!这里只关注ReentrantLock的实现逻辑。

        上述就是公平锁、非公平锁实现获取锁的主要流程,而针对每种锁来说,其实现方式有很大差别,主要就体现在各自实现类的lock()和tryAcquire()方法中。在sync的抽象类Sync及其抽象父类AbstractQueuedSynchronizer中,lock()方法和tryAcquire()方法被定义为抽象方法或者未实现,而是由具体子类去实现:

[java] view plain copy
  1. /** 
  2.  * Performs {@link Lock#lock}. The main reason for subclassing 
  3.  * is to allow fast path for nonfair version. 
  4.  */  
  5. abstract void lock();  
[java] view plain copy
  1.     /** 
  2.      * Attempts to acquire in exclusive mode. This method should query 
  3.      * if the state of the object permits it to be acquired in the 
  4.      * exclusive mode, and if so to acquire it. 
  5.      * 
  6.      * <p>This method is always invoked by the thread performing 
  7.      * acquire.  If this method reports failure, the acquire method 
  8.      * may queue the thread, if it is not already queued, until it is 
  9.      * signalled by a release from some other thread. This can be used 
  10.      * to implement method {@link Lock#tryLock()}. 
  11.      * 
  12.      * <p>The default 
  13.      * implementation throws {@link UnsupportedOperationException}. 
  14.      * 
  15.      * @param arg the acquire argument. This value is always the one 
  16.      *        passed to an acquire method, or is the value saved on entry 
  17.      *        to a condition wait.  The value is otherwise uninterpreted 
  18.      *        and can represent anything you like. 
  19.      * @return {@code true} if successful. Upon success, this object has 
  20.      *         been acquired. 
  21.      * @throws IllegalMonitorStateException if acquiring would place this 
  22.      *         synchronizer in an illegal state. This exception must be 
  23.      *         thrown in a consistent fashion for synchronization to work 
  24.      *         correctly. 
  25.      * @throws UnsupportedOperationException if exclusive mode is not supported 
  26.      */  
  27.     protected boolean tryAcquire(int arg) {  
  28.         throw new UnsupportedOperationException();  
  29.     }  
        下面,我们分别研究下非公平锁和公平锁的实现。


        四、非公平锁NonfairSync

        1、lock()方法

[java] view plain copy
  1. /** 
  2.  * Performs lock.  Try immediate barge, backing up to normal 
  3.  * acquire on failure. 
  4.  */  
  5. final void lock() {  
  6.     if (compareAndSetState(01))  
  7.         setExclusiveOwnerThread(Thread.currentThread());  
  8.     else  
  9.         acquire(1);  
  10. }  
        通过代码可以看到,非公平锁上来就无视等待队列的存在而抢占锁,通过基于CAS操作的compareAndSetState(0, 1)方法,试图修改当前锁的状态,这个0表示AbstractQueuedSynchronizer内部的一种状态,针对互斥锁则是尚未有线程持有该锁,而>=1则表示存在线程持有该锁,并重入对应次数,这个上来就CAS的操作也是非公共锁的一种体现,CAS操作成功的话,则将当前线程设置为该锁的唯一拥有者。

        抢占不成功的话,则调用父类的acquire()方法,按照上面讲的,继而会调用tryAcquire()方法,这个方法也是由最终实现类NonfairSync实现的,如下:

[java] view plain copy
  1. protected final boolean tryAcquire(int acquires) {  
  2.     return nonfairTryAcquire(acquires);  
  3. }  
        2、tryAcquire()

        而这个nonfairTryAcquire()方法实现如下:

[java] view plain copy
  1. /** 
  2.  * Performs non-fair tryLock.  tryAcquire is 
  3.  * implemented in subclasses, but both need nonfair 
  4.  * try for trylock method. 
  5.  */  
  6. final boolean nonfairTryAcquire(int acquires) {  
  7.     final Thread current = Thread.currentThread();  
  8.     int c = getState();  
  9.     if (c == 0) {  
  10.         if (compareAndSetState(0, acquires)) {  
  11.             setExclusiveOwnerThread(current);  
  12.             return true;  
  13.         }  
  14.     }  
  15.     else if (current == getExclusiveOwnerThread()) {  
  16.         int nextc = c + acquires;  
  17.         if (nextc < 0// overflow  
  18.             throw new Error("Maximum lock count exceeded");  
  19.         setState(nextc);  
  20.         return true;  
  21.     }  
  22.     return false;  
  23. }  
        还是上来先判断锁的状态,通过CAS来抢占,抢占成功,直接返回true,如果锁的持有者线程为当前线程的话,则通过累加状态标识重入次数。抢占不成功,或者锁的本身持有者不是当前线程,则返回false,继而后续通过进入等待队列的方式排队获取锁。可以通过以下简单的图来理解:

        


        五、公平锁FairSync

        1、lock()

        公平锁的lock()方法就比较简单了,直接调用acquire()方法,如下:

[java] view plain copy
  1. final void lock() {  
  2.     acquire(1);  
  3. }  

        2、tryAcquire()

        公平锁的tryAcquire()方法也相对较简单,如下:

[java] view plain copy
  1. /** 
  2.  * Fair version of tryAcquire.  Don't grant access unless 
  3.  * recursive call or no waiters or is first. 
  4.  */  
  5. protected final boolean tryAcquire(int acquires) {  
  6.     final Thread current = Thread.currentThread();  
  7.     int c = getState();  
  8.     if (c == 0) {  
  9.         if (!hasQueuedPredecessors() &&  
  10.             compareAndSetState(0, acquires)) {  
  11.             setExclusiveOwnerThread(current);  
  12.             return true;  
  13.         }  
  14.     }  
  15.     else if (current == getExclusiveOwnerThread()) {  
  16.         int nextc = c + acquires;  
  17.         if (nextc < 0)  
  18.             throw new Error("Maximum lock count exceeded");  
  19.         setState(nextc);  
  20.         return true;  
  21.     }  
  22.     return false;  
  23. }  
        当前线程会在得到当前锁状态为0,即没有线程持有该锁,并且通过!hasQueuedPredecessors()判断当前等待队列没有前继线程(也就是说,没有比我优先级更高的线程在请求锁了)获取锁的情况下,通过CAS抢占锁,并设置自己为锁的当前拥有者,当然,如果是重入的话,和非公平锁处理一样,通过累加状态位标记重入次数。

        而一旦等待队列中有等待者,或当前线程抢占锁失败,则它会乖乖的进入等待队列排队等待。公平锁的实现大致如下:

         


        六、默认实现

        ReentrantLock的默认实现为非公平锁,如下:

[java] view plain copy
  1. /** 
  2.  * Creates an instance of {@code ReentrantLock}. 
  3.  * This is equivalent to using {@code ReentrantLock(false)}. 
  4.  */  
  5. public ReentrantLock() {  
  6.     sync = new NonfairSync();  
  7. }  
        当然,你也可以通过另外一个构造方法指定锁的实现方式,如下:

[java] view plain copy
  1. /** 
  2.  * Creates an instance of {@code ReentrantLock} with the 
  3.  * given fairness policy. 
  4.  * 
  5.  * @param fair {@code true} if this lock should use a fair ordering policy 
  6.  */  
  7. public ReentrantLock(boolean fair) {  
  8.     sync = fair ? new FairSync() : new NonfairSync();  
  9. }  


        七、其它
        即便是公平锁,如果通过不带超时时间限制的tryLock()的方式获取锁的话,它也是不公平的,因为其内部调用的是sync.nonfairTryAcquire()方法,无论抢到与否,都会同步返回。如下:

[java] view plain copy
  1. public boolean tryLock() {  
  2.     return sync.nonfairTryAcquire(1);  
  3. }  

        但是带有超时时间限制的tryLock(long timeout, TimeUnit unit)方法则不一样,还是会遵循公平或非公平的原则的,如下:

[java] view plain copy
  1. public boolean tryLock(long timeout, TimeUnit unit)  
  2.         throws InterruptedException {  
  3.     return sync.tryAcquireNanos(1, unit.toNanos(timeout));  
  4. }  
        其它流程都比较简单,读者可自行翻阅Java源码查看!

这篇关于Java并发之ReentrantLock详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1111563

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

MySQL数据库双机热备的配置方法详解

《MySQL数据库双机热备的配置方法详解》在企业级应用中,数据库的高可用性和数据的安全性是至关重要的,MySQL作为最流行的开源关系型数据库管理系统之一,提供了多种方式来实现高可用性,其中双机热备(M... 目录1. 环境准备1.1 安装mysql1.2 配置MySQL1.2.1 主服务器配置1.2.2 从

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

MyBatis常用XML语法详解

《MyBatis常用XML语法详解》文章介绍了MyBatis常用XML语法,包括结果映射、查询语句、插入语句、更新语句、删除语句、动态SQL标签以及ehcache.xml文件的使用,感兴趣的朋友跟随小... 目录1、定义结果映射2、查询语句3、插入语句4、更新语句5、删除语句6、动态 SQL 标签7、ehc

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置