ReentrantLock

  1. ReentrantLock
    1. AQS
      1. 核心组成部分
      2. AQS执行逻辑
      3. AQS特点
    2. 公平锁和非公平锁
    3. 可重入锁
  2. ReentrantLock是什么?
    1. 方法介绍
    2. 简单使用案例

ReentrantLock

ReentrantLock是一种基于AQS(Abstract Queued Synchronizer)的应用实现,是JDK中一种线程并发的同步手段。

它的功能类似于sychronized,是一种互斥锁,可以保证线程安全。

sychronized是关键字,是JVM级别实现的悲观锁。

ReentrantLock是类级别实现的锁。

基本使用

ReentrantLock() 构造方法

  • 传入参数 false/true 不传默认为false 使用的是非公平锁,true为公平锁实现

lock 当前线程获取锁方法

unlock 当前线程释放锁方法,如果当前线程不持有锁,则抛出IllegalMonitorStateException异常

为了确保线程不会因为异常而未解锁而导致的死锁问题,必须在finally语句块中解锁

同时为了保证线程能获取到锁,lock方法与try语句块之间不应该有其它代码。

import java.util.concurrent.locks.ReentrantLock;

public class LockDemo {

    private static ReentrantLock fairLock = new ReentrantLock(true);

    private static ReentrantLock nonFairLock = new ReentrantLock();

    public static void main(String[] args) {

        System.out.println("非公平锁竞争:");

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                nonFairLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName()+"获取到锁");
                }finally {
                    nonFairLock.unlock();
                }
            }).start();
        }

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("公平锁竞争:");

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                fairLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName()+"获取到锁");
                }finally {
                    fairLock.unlock();
                }
            }).start();
        }


    }

}

非公平锁竞争:
Thread-0获取到锁
Thread-1获取到锁
Thread-2获取到锁
Thread-3获取到锁
Thread-4获取到锁
Thread-5获取到锁
Thread-6获取到锁
Thread-7获取到锁
Thread-8获取到锁
Thread-9获取到锁
公平锁竞争:
Thread-10获取到锁
Thread-11获取到锁
Thread-12获取到锁
Thread-13获取到锁
Thread-14获取到锁
Thread-15获取到锁
Thread-16获取到锁
Thread-17获取到锁
Thread-18获取到锁
Thread-19获取到锁

可见两种锁的结果是不同的,公平锁的获取锁顺序是有序的,而非公平锁则是无序的,后面将解释为什么两种锁的竞争结果不同。

AQS

由于ReentrantLock同步器底层是由AQS构建的,因此我们需要简单了解一下AQS

AQS 抽象队列式同步器

场景引入

有四个线程由于业务需求要同时占用某资源,但该资源在同一时刻只能被其中一个线程占用。也就是我们的共享资源场景。

AQS是一个集同步状态管理,线程阻塞,线程释放及队列管理功能于一身的同步框架。

核心思想:

提供了一个基于FIFO队列的框架,用于构建锁和其它同步器,这个框架用一个单一的整型变量state来表示同步状态。被放入队列的线程会保持阻塞直到被前驱结点唤醒。(确保了有序性,减少了不必要的竞争)

该队列中只有队首结点有资格竞争锁

核心组成部分

  1. 同步状态(State):AQS使用一个volatile变量来表示同步状态,这个状态可以表示锁的获取次数或者资源的可用性。

    1. **getState()**:获取当前的同步状态。
    2. **setState(int newState)**:设置当前的同步状态。
    3. **compareAndSetState(int expect, int update)**:如果当前状态值等于预期值
  2. 节点(Node)和队列:当线程尝试获取同步状态失败时,AQS会将这些线程封装成节点(Node)并加入到一个FIFO队列中。这个队列是AQS中管理线程等待的主要结构。

  3. 独占模式和共享模式:AQS支持两种同步模式。独占模式下,每次只有一个线程能够执行(ReentrantLock就是这种);共享模式下,多个线程可以同时执行。

  4. 获取和释放方法:AQS定义了acquirerelease等方法,允许线程以独占或共享的方式来获取和释放同步状态。

  5. 等待和通知机制:AQS还提供了条件变量(Condition)的支持,允许线程在特定条件下等待,或者在条件满足时被通知唤醒。

AQS执行逻辑

  1. 线程1进入
  2. 如果锁可用(同步状态允许),直接获取锁
  3. 锁不可用(被占用),封装为Node放入FIFO队列并阻塞
    1. 非公平锁:线程2进入,尝试获取锁(自旋CAS),如果竞争失败,放入FIFO队尾,阻塞等待
    2. 公平锁:线程2进入,检查等待队列,确保无线程等待,若存在线程等待,加入队尾等待
  4. 线程1释放锁,更新同步状态,唤醒队列中下一个等待线程
  5. 线程2被唤醒,尝试获取锁

AQS特点

  • AQS利用CAS操作来管理同步状态,提高了并发效率(自旋锁+传统阻塞

    CAS操作在不放弃CPU时间片的情况下尝试重复获取锁(减少线程上下文切换)

  • 同时AQS是许多同步器的基础,如ReentrantLock,Semaphore,CountDownLatch等

  • AQS使用模板方法模式,允许开发者通过继承AQS并重写指定方法来实现自定义的同步器

  • AQS支持条件变量,即允许线程在特定条件下等待,在特定条件满足时被唤醒

公平锁和非公平锁

公平锁:先来的线程先执行

在申请锁时如果有其它线程占用了锁,则进行排队等待

  • 优点:线程资源都能得到分配,不会出现长时间得不到分配的情况
  • 缺点:吞吐量会下降许多,队列里除了第一个线程,其它线程都会阻塞,CPU唤醒阻塞线程开销较大

非公平锁: 后来的线程有可能插队执行

  • 优点: 减少CPU唤醒线程开销,整体吞吐效率会高一些,减少了线程上下文切换的开销
  • 缺点: 被阻塞的线程可能会因为被插队而长时间获取不到资源

由此可见,我们之前的代码由于每个线程执行时间很短,竞争并不激烈。因此非公平锁模式下会经常发生插队,所以线程执行并不是FIFO的。

而公平锁模式下,线程的执行是遵循FIFO的。

可重入锁

可重入锁,也被称为递归锁,ReentrantLock就是典型的可重入锁。

核心:可重入锁的设计使得当同一个线程再次进入由它持有锁的代码块时,能再次获取锁并不被阻塞

ReentrantLock对于AQS的state状态的定义是线程获取锁的次数,基于这种设计,ReentrantLock具备记忆锁次数的特性,因此当重复获取锁时可以通过state的值来判断当前线程是否持有锁。

ReentrantLock是什么?

基于上面的概念介绍,我们再来认识一下reentrantLock是什么。

  • 一个基于AQS实现的同步器
  • 支持公平锁和非公平锁两种模式
  • 互斥锁(只能执行一个线程)
  • 可重入锁

方法介绍

方法 注释
lock() 获取锁的方法,尝试获取锁,如果锁被占用,则阻塞等待。
unlock() 一个线程在完成其临界区的代码后,应该调用这个方法来释放锁。
tryLock() 尝试立即获取到锁,如果锁可用,返回true,如果锁被占用,返回false。
tryLock(long timeout, TimeUnit unit) 这个方法尝试在给定的等待时间内获取锁,如果在指定的时间内锁变得可用,并且当前线程未被中断,它将返回true。
lockInterruptibly() 这个方法类似于lock(),但它允许在等待锁的过程中响应中断。如果当前线程在进入此方法时已经设置了中断状态,或者在等待锁的过程中被中断,它将抛出InterruptedException。
newCondition() 这个方法返回一个与该锁绑定的新的Condition实例。Condition提供了一种在某个条件成立之前挂起线程的方法,并且在条件成立时能够唤醒一个或多个线程。(类似生产者消费者wait notify)
getHoldCount() 当前线程调用 lock() 方法的次数。
getQueueLength() 当前正在等待获取 Lock 锁的线程的估计数。
getWaitQueueLength(Condition condition) 当前正在等待状态的线程的估计数,需要传入 Condition 对象。
hasWaiters(Condition condition) 查询是否有线程正在等待与 Lock 锁有关的 Condition 条件。
hasQueuedThread(Thread thread) 查询指定的线程是否正在等待获取 Lock 锁。
hasQueuedThreads() 查询是否有线程正在等待获取此锁定。
isFair() 判断当前 Lock 锁是不是公平锁。
isHeldByCurrentThread() 查询当前线程是否持有锁。

简单使用案例

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantExample {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    // 使用ReentrantLock增加计数
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantExample example = new ReentrantExample();

        // 创建两个线程,模拟并发环境
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        t1.start();
        t2.start();

        // 等待两个线程完成
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出最终的计数值
        System.out.println("Count is: " + example.count);
    }
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com

文章标题:ReentrantLock

字数:2.3k

本文作者:Os467

发布时间:2024-05-16, 21:31:48

最后更新:2024-05-16, 21:32:02

原始链接:https://os467.github.io/2024/05/16/ReentrantLock/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

×

喜欢就点赞,疼爱就打赏