深入理解并发编程之 ReentrantLock 的实现原理

目录

深入理解并发编程之 ReentrantLock 的实现原理

一、ReentrantLock简介

二、ReentrantLock的实现原理

三、前端展示(使用 Vue)

四、ReentrantLock的应用场景


在并发编程领域,ReentrantLock是一个非常重要的锁机制。这篇博客我们将深入探讨并发编程篇 16 中涉及的ReentrantLock的实现原理。

一、ReentrantLock简介

ReentrantLock是一个可重入的互斥锁,它和synchronized关键字类似,但提供了更强大的功能,比如可以尝试非阻塞地获取锁、能被中断地获取锁以及超时获取锁等。它位于java.util.concurrent.locks包中。

二、ReentrantLock的实现原理

  1. 基于 AQS(AbstractQueuedSynchronizer)
    ReentrantLock内部通过一个Sync类来继承AQS,实现锁的基本逻辑。Sync有两个具体的实现类,NonfairSync(非公平锁)和FairSync(公平锁)。

以下是ReentrantLock的基本使用代码示例:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();

        // 线程1尝试获取锁并执行任务
        Thread thread1 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("线程1获取到锁,执行任务...");
                // 模拟任务执行
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        // 线程2尝试获取锁并执行任务
        Thread thread2 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("线程2获取到锁,执行任务...");
            } finally {
                lock.unlock();
            }
        });

        thread1.start();
        // 稍微延迟启动线程2,模拟并发情况
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
    }
}

  1. 获取锁的过程

    • NonfairSync中,lock()方法首先会通过CAS(Compare and Swap)操作尝试设置AQS的状态(state)为 1,如果成功,表示获取到锁,设置当前线程为锁的拥有者。如果CAS失败,会调用acquire(1)方法,该方法会再次尝试CAS获取锁,如果还是失败,则将当前线程包装成Node节点加入到AQS的等待队列中。
    • FairSync中,获取锁的过程类似,但在lock()方法中会先检查等待队列中是否有前驱节点,如果有,则当前线程不会尝试获取锁,而是加入到等待队列末尾,保证了公平性。
  2. 释放锁的过程
    当线程执行完任务,调用unlock()方法时,ReentrantLock会调用AQSrelease(1)方法。在release方法中,首先会尝试通过CASstate减 1,如果state变为 0,表示锁已经完全释放,此时会唤醒等待队列中的头节点线程。

三、前端展示(使用 Vue)

我们可以创建一个简单的 Vue 组件来模拟ReentrantLock的获取和释放情况:

<template>
  <div>
    <h2>ReentrantLock 演示</h2>
    <button @click="acquireLock">线程1获取锁</button>
    <button @click="acquireLock2">线程2获取锁</button>
    <button @click="releaseLock" :disabled="!lockHeld">释放锁</button>
  </div>
</template>

<script>
import ReentrantLock from './ReentrantLock.js'; // 假设这里是后端定义的 ReentrantLock 类的导入路径

export default {
  data() {
    return {
      reentrantLock: new ReentrantLock(),
      lockHeld: false
    };
  },
  methods: {
    acquireLock() {
      try {
        this.reentrantLock.lock();
        this.lockHeld = true;
        console.log('线程1获取到锁');
      } catch (error) {
        console.error('线程1获取锁失败', error);
      }
    },
    acquireLock2() {
      try {
        this.reentrantLock.lock();
        this.lockHeld = true;
        console.log('线程2获取到锁');
      } catch (error) {
        console.error('线程2获取锁失败', error);
      }
    },
    releaseLock() {
      try {
        this.reentrantLock.unlock();
        this.lockHeld = false;
        console.log('锁已释放');
      } catch (error) {
        console.error('释放锁失败', error);
      }
    }
  }
};
</script>

<style>
/* 样式代码 */
</style>

四、ReentrantLock的应用场景

  1. 在需要更灵活的锁控制的多线程环境中,ReentrantLock可以替代synchronized关键字。例如,在一个多线程的资源访问场景中,如果某个线程获取锁后可能需要长时间执行任务,使用ReentrantLock可以更好地控制其他线程的等待和唤醒。
  2. 当需要实现公平锁机制时,FairSync实现的ReentrantLock可以保证线程按照请求锁的顺序依次获取锁,避免了饥饿现象。

总之,理解ReentrantLock的实现原理对于编写高效、可靠的并发程序非常重要,它为我们在处理复杂的多线程同步问题时提供了更多的选择和控制手段。

猜你喜欢

转载自blog.csdn.net/m0_57836225/article/details/143472007