Java中的ReentrantLock实现原理及代码演示

 更新时间:2024年01月13日 09:39:17   作者:java架构师-太阳  
这篇文章主要介绍了Java中的ReentrantLock实现原理及代码演示,非公平锁 如果已经进入队列,链表里面的线程是先进先出,如果已经释放了锁,在抢占锁时,链表里面的头结点和还没有入队列的线程抢锁,需要的朋友可以参考下

介绍

互斥锁 实现Lock接口 并且最好在 finally 块中释放

公平锁 非公平锁 如果已经进入队列,链表里面的线程是先进先出,如果已经释放了锁,在抢占锁时,链表里面的头结点和还没有入队列的线程抢锁

使用代码

public class Test {
    public static void main(String[] args) {
       Task task = new Task();
       for(int i = 0; i < 10; i++) {
          new Thread(task).start();
       }
    }
}
class Task implements Runnable {
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        lock.lock();
        try {
            Thread.sleep(1000);
            System.out.println("业务代码");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

实现原理

采用AQS+CAS+LockSupport用来阻塞和唤醒线程)

ReentrantLock有三类内部类,实现都在其内部类Sync中,默认是使用非公平锁NonFairSync。

非公平锁可提高效率,在可重入锁时可以减少线程切换开销。可以通过构造方法切换公平和非公平

Sync父类AbstractQueuedSynchronize(AQS) 此处的锁具备synchronized功能,即可以阻塞一个线程。

为了实现一把具有阻塞或唤醒功能的锁,需要几个核心要素

(1) state变量,标记锁状态。至少有两个值0/1。对state的操作,使用CAS保证线程安全

  • AbstractQueuedSynchronizer类里有变量private volatile int state 记录锁状态
  • state=0,没有线程持有锁,exclusiveOwnerThread=null
  • state=1,有一个线程持有锁,exclusiveOwnerThread=该线程
  • state > 1,说明该线程重入了该锁,等于几就重入了几次

(2) 需要记录当前是哪个线程持有锁

  • AbstractOwnableSynchronizer里面有变量private transient Thread exclusiveOwnerThread;记录锁持有的线程

(3) 需要底层支持对一个线程进行阻塞或唤醒操作

  • public native void unpark(Object thread); // 唤醒某一个线程
  • public native void park(boolean isAbsolute, long time); // 阻塞某一个线程 实现一个线程对另外一个线程的精准唤醒
  • 一般使用LockSupport的工具类,对上面两个方法进行了封装
  • 当前线程中调用park()就会被阻塞 另一个线程调用unpark(Thread t)传入被阻塞线程就可唤醒阻塞在park()地方的线程

(4) 需要有一个队列维护所有阻塞的线程。这个队列也必须是线程安全的无锁队列,也需要使用CAS对队列进行增加或删除

public abstract class AbstractQueuedSynchronizer {
  // 双向链表
  static final class Node {
  volatile Thread thread;  // 每个Node对应一个被阻塞的线程
  volatile Node prev;      // 前一个
  volatile Node next;      // 后一个
 }
private transient volatile Node head;   // 头
private transient volatile Node tail;   // 尾
}

阻塞队列是整个AQS核心中的核心。如下图所示,head指向双向链表头部,tail指向双向链表尾部。入队就是把新的Node加到tail后面,然后对tail进行CAS操作;

出队就是对head进行CAS操作,把head向后移一个位置

在这里插入图片描述

初始的时候,head=tail=NULL;然后,在往队列中加入阻塞的线程时,会新建一个空的Node,让head和tail都指向这个空Node;之后,在后面加入被阻塞的线程对象。

所以,当head=tail的时候,说明队列为空。

到此这篇关于Java中的ReentrantLock实现原理及代码演示的文章就介绍到这了,更多相关ReentrantLock实现原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring Cache整合Redis实现方法详解

    Spring Cache整合Redis实现方法详解

    这篇文章主要介绍了Spring Cache整合Redis实现方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • Mybatis如何传入多个参数的实现代码

    Mybatis如何传入多个参数的实现代码

    这篇文章主要介绍了Mybatis如何传入多个参数的实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • SpringBoot读取application.yml作为配置的处理

    SpringBoot读取application.yml作为配置的处理

    SpringBoot支持将配置放在application.yml或application.properties中,自动加载并注入到应用中,使用@Value或@ConfigurationProperties读取配置,推荐使用后者处理结构化配置
    2026-04-04
  • Java集合类的组织结构和继承、实现关系详解

    Java集合类的组织结构和继承、实现关系详解

    这篇文章主要介绍了Java集合类的组织结构和继承、实现关系,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • IntelliJ IDEA Java项目手动添加依赖 jar 包的方法(图解)

    IntelliJ IDEA Java项目手动添加依赖 jar 包的方法(图解)

    这篇文章主要介绍了IntelliJ IDEA Java项目手动添加依赖 jar 包,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-04-04
  • java使用EasyExcel实现Sheet的复制与填充

    java使用EasyExcel实现Sheet的复制与填充

    EasyExcel是一个非常有用的工具,它提供了强大的模板填充功能,可以轻松解决各种业务需求,本文主要为大家介绍了如何使用EasyExcel实现模板Sheet复制与填充,需要的可以参考下
    2023-10-10
  • Java获取两个字符串中最大相同子串的方法

    Java获取两个字符串中最大相同子串的方法

    今天小编就为大家分享一篇Java获取两个字符串中最大相同子串的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-07-07
  • Java线程通讯的实现方法总结

    Java线程通讯的实现方法总结

    线程通讯指的是多个线程之间通过共享内存或消息传递等方式来协调和同步它们的执行,线程通讯的实现方式主要有以下两种:共享内存和消息传递,本文详细介绍了Java线程是如何通讯的,感兴趣的同学可以参考阅读
    2023-05-05
  • 教你如何编写简单的网络爬虫

    教你如何编写简单的网络爬虫

    实际的爬虫是从一系列的种子链接开始。种子链接是起始节点,种子页面的超链接指向的页面是子节点(中间节点),对于非html文档,如excel等,不能从中提取超链接,看做图的终端节点
    2013-10-10
  • idea编译器工程out目录修改方法步骤

    idea编译器工程out目录修改方法步骤

    多个工程在一个文件夹下,有时会变为所有的工程只用一个out文件夹,这时运行会出错,所以本文就来介绍一下out目录修改,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09

最新评论