ReentrantLock入门:核心特性与基本使用

一、前言

在并发编程领域,多线程对共享资源的竞争是核心问题,而锁是解决该问题的关键工具。 synchronized 作为 Java 内置的隐式锁,虽使用便捷,但存在功能局限性,比如不支持可中断获取锁、无法设置锁超时等。 ReentrantLock 作为 JUC 包提供的显式锁,正是为了弥补这些不足而诞生。本文将从基础特性和使用方式入手,带大家快速入门 ReentrantLock,同时为后续的源码解析和实战应用做好铺垫。

二、 核心特性

1. 可重入性

可重入性指同一线程可以多次获取同一把锁而不会发生死锁。ReentrantLock 内部通过维护一个锁重入计数器实现该特性,线程首次获取锁时计数器置为 1,后续每次重入计数器加 1,每次释放锁时计数器减 1,直至计数器为 0 时锁才真正释放。

2. 可中断性

ReentrantLock 支持通过 lockInterruptibly() 方法实现可中断的锁获取。当线程在等待获取锁的过程中,若其他线程调用了该线程的 interrupt() 方法,该线程会抛出 InterruptedException 并终止锁等待,这一特性可有效避免死锁。

3. 公平 / 非公平锁策略

  • **公平锁:**线程获取锁的顺序遵循 "先来后到",即等待时间越长的线程越先获取锁,能避免线程饥饿,但会因维护队列顺序带来一定性能损耗。

  • **非公平锁:**线程获取锁时会先尝试直接抢占,抢占失败再进入等待队列,默认采用此策略,吞吐量更高,但可能导致部分线程长期无法获取锁。

4. 超时获取锁

通过 tryLock(long time, TimeUnit unit) 方法,线程可以在指定时间内尝试获取锁,若超时仍未获取到则返回 false ,避免线程永久阻塞在锁获取上。

三、基本使用

1. 基础使用范式

ReentrantLock 为显式锁,需手动调用 lock() 获取锁、 unlock() 释放锁,且必须在 try-finally 块中使用,确保锁最终能释放,防止死锁。

java 复制代码
import java.util.concurrent.locks.ReentrantLock;
public class BasicLockDemo {
    private static final ReentrantLock lock = new ReentrantLock();
    private static int count = 0;
    public static void increment() {
        // 获取锁
        lock.lock();
        try {
            count++;
            System.out.println(Thread.currentThread().getName() + " 执行累加,count = " + count);
        } finally {
            // 释放锁,必须在finally中执行
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(BasicLockDemo::increment).start();
        }
    }
}

2. 可中断锁的使用

使用 lockInterruptibly() 实现可中断锁获取,需处理 InterruptedException 异常:

java 复制代码
import java.util.concurrent.locks.ReentrantLock;
public class InterruptibleLockDemo {
    private static final ReentrantLock lock = new ReentrantLock();
    public static void doTask() {
        try {
            // 可中断的锁获取
            lock.lockInterruptibly();
            try {
                System.out.println(Thread.currentThread().getName() + " 获取到锁,开始执行任务");
                Thread.sleep(2000);
            } finally {
                lock.unlock();
                System.out.println(Thread.currentThread().getName() + " 释放锁");
            }
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + " 被中断,未获取到锁");
            Thread.currentThread().interrupt();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(InterruptibleLockDemo::doTask, "线程1");
        Thread t2 = new Thread(InterruptibleLockDemo::doTask, "线程2");
        t1.start();
        Thread.sleep(500);
        t2.start();
        // 中断线程2
        t2.interrupt();
    }
}

3. 超时获取锁

通过 tryLock() 方法设置锁获取超时时间:

java 复制代码
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TimeoutLockDemo {
    private static final ReentrantLock lock = new ReentrantLock();
    public static void tryGetLock() {
        try {
            // 尝试在1秒内获取锁
            if (lock.tryLock(1, TimeUnit.SECONDS)) {
                try {
                    System.out.println(Thread.currentThread().getName() + " 成功获取锁,执行任务");
                    Thread.sleep(2000);
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println(Thread.currentThread().getName() + " 超时未获取到锁");
            }
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + " 获取锁时被中断");
        }
    }
    public static void main(String[] args) {
        new Thread(TimeoutLockDemo::tryGetLock, "线程A").start();
        new Thread(TimeoutLockDemo::tryGetLock, "线程B").start();
    }
}

4. 公平锁的创建

通过 ReentrantLock 的有参构造函数指定锁的公平性:

java 复制代码
// 创建公平锁,默认创建的是非公平锁
ReentrantLock fairLock = new ReentrantLock(true);

四、入门案例

分别使用 synchronized 和 ReentrantLock 实现多线程累加,对比两种锁的使用差异:

1. synchronized 实现

java 复制代码
public class SynchronizedCounter {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
    public synchronized int getCount() {
        return count;
    }
    public static void main(String[] args) throws InterruptedException {
        SynchronizedCounter counter = new SynchronizedCounter();
        for (int i = 0; i < 1000; i++) {
            new Thread(counter::increment).start();
        }
        Thread.sleep(1000);
        System.out.println("最终计数:" + counter.getCount());
    }
}

2. ReentrantLock 实现

java 复制代码
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockCounter {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ReentrantLockCounter counter = new ReentrantLockCounter();
        for (int i = 0; i < 1000; i++) {
            new Thread(counter::increment).start();
        }
        Thread.sleep(1000);
        System.out.println("最终计数:" + counter.getCount());
    }
}

3. 实现差异对比

  • synchronized 是隐式锁,无需手动释放,代码简洁但功能单一;

  • ReentrantLock 是显式锁,需手动管理锁的获取与释放,但支持可中断、超时获取等高级功能。

五、总结

本文介绍了 ReentrantLock 的核心特性和基础使用方式,包括可重入性、可中断性、公平 / 非公平锁策略、超时获取锁,以及对应的代码实现和入门案例。通过对比 synchronized ,能明显感受到 ReentrantLock 在功能灵活性上的优势。

相关推荐
资生算法程序员_畅想家_剑魔2 小时前
Java常见技术分享-27-事务安全-事务日志-事务日志框架
java·开发语言
你撅嘴真丑2 小时前
短信计费 和 甲流病人初筛
数据结构·c++·算法
古城小栈2 小时前
内存对决:rust、go、java、python、nodejs
java·golang·rust
CAAS_IFR_zp2 小时前
BEAUT:胆汁酸酶注释
数据结构
予枫的编程笔记2 小时前
【Java 进阶3】Kafka从入门到实战:全面解析分布式消息队列的核心与应用
java·分布式·kafka
Wang153010 小时前
jdk内存配置优化
java·计算机网络
散峰而望10 小时前
【算法竞赛】C++函数详解:从定义、调用到高级用法
c语言·开发语言·数据结构·c++·算法·github
0和1的舞者11 小时前
Spring AOP详解(一)
java·开发语言·前端·spring·aop·面向切面
Wang153011 小时前
Java多线程死锁排查
java·计算机网络