Java使用重入锁实现线程同步的示例代码

 更新时间:2025年07月07日 09:28:36   作者:Katie。  
在多线程环境中,为保证对共享资源的安全访问,常用的同步手段是 synchronized 关键字,但 synchronized 存在局限,本项目旨在通过示例演示如何使用 ReentrantLock 实现线程同步,替代 synchronized,并展示其高级功能,需要的朋友可以参考下

一、项目背景详细介绍

在多线程环境中,为保证对共享资源的安全访问,常用的同步手段是 synchronized 关键字。但 synchronized 存在以下局限:

  • 灵活性较低:不能尝试超时获取锁,也无法中断获取锁的线程;
  • 可见性:无法查看当前锁是否被占用或等待队列情况;
  • 公平性控制:无法直接控制锁的公平或非公平策略。

Java 5 引入了更强大的 java.util.concurrent.locks 包,其中的 ReentrantLock(可重入锁)在功能和性能上均优于内置锁。它提供:

  • 尝试获取锁tryLock()tryLock(timeout, unit)
  • 可中断的锁获取lockInterruptibly()
  • 公平锁:通过构造函数选择公平或非公平策略
  • 监视器查询getHoldCount()isLocked()getQueueLength() 等方法

本项目旨在通过示例演示如何使用 ReentrantLock 实现线程同步,替代 synchronized,并展示其高级功能,如超时尝试获取锁、公平策略和中断响应。

二、项目需求详细介绍

基本互斥访问

  • 使用 ReentrantLock 替代 synchronized,在多个线程间安全地更新同一共享变量或数据结构;
  • 提供示例:多线程对同一计数器或共享列表进行增删操作。

超时获取锁

  • 演示 tryLock(long timeout, TimeUnit unit) 用法,当锁长时间被占用时抛出或走备用逻辑;

可中断锁获取

  • 演示 lockInterruptibly(),在等待锁期间响应中断,避免因锁阻塞导致的无法取消;

公平锁与非公平锁

  • 对比默认(非公平)锁和通过 new ReentrantLock(true) 创建的公平锁在高并发场景下的性能及线程调度差异;

锁状态监控

  • 使用 getHoldCount()getQueueLength()hasQueuedThreads() 等方法,实时查询锁的占用与等待情况,打印日志监控;

示例应用

  • 实现一个带超时和中断功能的共享资源访问类 SharedResource
  • 编写多线程测试,模拟高并发下的锁获取、超时回退和中断场景;

配置可控

  • 通过构造参数或配置文件动态切换公平性、超时阈值等;

文档与示例

  • 在 README 中给出代码调用示例及注意事项;
  • 对比 synchronizedReentrantLock 的使用差异。

三、相关技术详细介绍

java.util.concurrent.locks.ReentrantLock

  • 基本方法lock()unlock()lockInterruptibly()tryLock()tryLock(timeout, unit)
  • 构造参数new ReentrantLock()(非公平锁)、new ReentrantLock(true)(公平锁);

Condition 接口

  • 通过 lock.newCondition() 创建条件变量,替代 wait/notify,支持多条件队列;
  • 方法:await()signal()signalAll()

锁监控与诊断

  • getHoldCount():返回当前线程重入次数;
  • isLocked():锁是否被任意线程占用;
  • hasQueuedThreads()getQueueLength():等待锁的线程信息;

中断与超时

  • lockInterruptibly() 在锁等待时可响应中断;
  • tryLock(timeout, unit) 在指定时长内等待,超时后返回 false

多线程测试

  • 使用 ExecutorService 启动多个线程;
  • 使用 CountDownLatchCyclicBarrier 协调线程启动同步测试;
  • 记录锁获取次数与失败次数进行统计。

四、实现思路详细介绍

SharedResource 类设计

  • 内部包含 private final ReentrantLock lock; 和可选的 Condition
  • 提供方法:
void safeIncrement() { lock.lock(); try { /* 更新共享计数 */ } finally { lock.unlock(); } }
boolean trySafeIncrement(long timeout, TimeUnit unit) { if (lock.tryLock(timeout, unit)) { try { ... } finally { lock.unlock(); } } else { /* 超时逻辑 */ } }
void interruptibleAccess() throws InterruptedException { lock.lockInterruptibly(); try { ... } finally { lock.unlock(); } }
  • 若需要多个条件,可创建 Condition notEmptynotFull 并在方法中配合使用。

公平与非公平锁对比

  • 在测试中构造两种 SharedResource,公平锁与非公平锁,分别运行相同并发测试,比较吞吐量与线程饥饿情况。

监控锁状态

  • 在方法中或监控线程里定期调用 lock.getQueueLength()lock.hasQueuedThreads() 并打印,观察等待线程数;
  • 结合 lock.getHoldCount() 了解重入深度。

多线程测试

  • 使用 ExecutorService 和多个工作线程不断调用不同模式的方法;
  • 使用 CountDownLatch 保证开始同步,使用 AtomicInteger 统计成功与超时/中断次数;

文档示例

  • 在 README 中说明各模式使用场景和注意事项,例如必须在 finally 块中 unlock(),避免死锁。
/*
 * =====================================================
 * File: SharedResource.java
 * 共享资源类,使用 ReentrantLock 实现多种同步策略
 * =====================================================
 */
package com.example.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class SharedResource {
    private int counter = 0;
    private final ReentrantLock lock;
    private final Condition notZero;

    /**
     * 构造函数:可指定是否公平锁
     */
    public SharedResource(boolean fair) {
        this.lock = new ReentrantLock(fair);
        this.notZero = lock.newCondition();
    }

    /**
     * 基本互斥:安全地递增 counter
     */
    public void safeIncrement() {
        lock.lock();
        try {
            counter++;
            System.out.printf("%s incremented to %d%n",
                Thread.currentThread().getName(), counter);
            notZero.signalAll();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 带超时尝试获取锁的递增
     */
    public boolean trySafeIncrement(long timeout, TimeUnit unit) {
        boolean acquired = false;
        try {
            acquired = lock.tryLock(timeout, unit);
            if (acquired) {
                counter++;
                System.out.printf("%s timed increment to %d%n",
                    Thread.currentThread().getName(), counter);
                notZero.signalAll();
                return true;
            } else {
                System.out.printf("%s failed to acquire lock in %d %s%n",
                    Thread.currentThread().getName(), timeout, unit);
                return false;
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.printf("%s interrupted while waiting%n",
                Thread.currentThread().getName());
            return false;
        } finally {
            if (acquired) lock.unlock();
        }
    }

    /**
     * 可中断地获取锁并等待 counter > 0 后消费
     */
    public int interruptibleConsume() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            while (counter == 0) {
                System.out.printf("%s waiting for counter > 0%n",
                    Thread.currentThread().getName());
                notZero.await();
            }
            counter--;
            System.out.printf("%s consumed to %d%n",
                Thread.currentThread().getName(), counter);
            return counter;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 监控方法:打印当前锁状态
     */
    public void printLockStatus() {
        System.out.printf("Lock held by thread: %s, holdCount=%d, queuedThreads=%d%n",
            lock.isLocked() && lock.isHeldByCurrentThread()
                ? Thread.currentThread().getName()
                : "other",
            lock.getHoldCount(),
            lock.getQueueLength());
    }
}


/*
 * =====================================================
 * File: LockDemo.java
 * 演示:多线程调用 SharedResource 不同方法
 * =====================================================
 */
package com.example.lock;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class LockDemo {
    public static void main(String[] args) throws InterruptedException {
        SharedResource fairResource = new SharedResource(true);
        SharedResource unfairResource = new SharedResource(false);

        ExecutorService exec = Executors.newFixedThreadPool(6);
        CountDownLatch startLatch = new CountDownLatch(1);
        AtomicInteger successCount = new AtomicInteger(0);
        AtomicInteger failCount = new AtomicInteger(0);

        // 创建 2 个常规增量任务
        for (int i = 0; i < 2; i++) {
            exec.submit(() -> {
                await(startLatch);
                fairResource.safeIncrement();
            });
        }

        // 创建 2 个带超时尝试锁任务
        for (int i = 0; i < 2; i++) {
            exec.submit(() -> {
                await(startLatch);
                if (fairResource.trySafeIncrement(500, TimeUnit.MILLISECONDS)) {
                    successCount.incrementAndGet();
                } else {
                    failCount.incrementAndGet();
                }
            });
        }

        // 创建 2 个可中断消费任务
        for (int i = 0; i < 2; i++) {
            exec.submit(() -> {
                await(startLatch);
                try {
                    unfairResource.interruptibleConsume();
                } catch (InterruptedException e) {
                    System.out.printf("%s interrupted%n",
                        Thread.currentThread().getName());
                }
            });
        }

        // 启动所有任务
        startLatch.countDown();

        // 等待一段时间后中断消费任务
        Thread.sleep(1000);
        exec.shutdownNow();
        exec.awaitTermination(5, TimeUnit.SECONDS);

        System.out.printf("TryLock successes: %d, failures: %d%n",
            successCount.get(), failCount.get());
    }

    private static void await(CountDownLatch latch) {
        try {
            latch.await();
        } catch (InterruptedException ignored) {}
    }
}

代码详细解读

SharedResource

  • safeIncrement():使用 lock.lock()unlock() 实现基本互斥,并在 notZero 条件上唤醒等待的消费者。
  • trySafeIncrement(timeout, unit):使用 tryLock(timeout, unit) 带超时尝试获取锁,超时后返回失败逻辑。
  • interruptibleConsume():使用 lock.lockInterruptibly(),在等待中可响应中断,配合 notZero.await() 等待条件。
  • printLockStatus():演示查询锁状态的方法,包括持有计数和等待队列长度。

LockDemo

使用 ExecutorService 启动 6 个线程:

  • 2 个调用 safeIncrement()
  • 2 个调用 trySafeIncrement(500ms),统计成功与失败次数;
  • 2 个调用 interruptibleConsume(),并在主线程中断它们,演示可中断锁获取;
  • 使用 CountDownLatch 保证所有线程同时开始。
  • 程序运行 1 秒后调用 shutdownNow() 中断消费任务,并打印 tryLock 的统计结果。

项目详细总结

本示例通过 ReentrantLock 展示了:

  • 基本互斥:与 synchronized 类似,但可以更灵活地控制锁释放时机。
  • 超时获取锁tryLock(timeout, unit) 避免长时间阻塞,方便实现备用逻辑。
  • 可中断锁lockInterruptibly() 在等待锁时响应中断,提高了取消能力。
  • 公平与非公平:可通过构造函数选择公平策略,避免线程饥饿。
  • 锁监控getHoldCount()getQueueLength() 等方法便于在运行时诊断锁状态。

项目常见问题及解答

为何要在 finallyunlock()
避免在执行过程中抛出异常导致锁未释放,进而引发死锁。

tryLock 获不到锁后还能重试吗?
可以在代码中判断失败后循环调用,或结合退避机制重试。

公平锁性能更差吗?
是的,公平锁会增加上下文切换成本,一般在需要严格顺序时使用,否则推荐默认非公平锁。

lockInterruptibly 如何正确处理中断?
调用方法需声明 throws InterruptedException,在捕获后可执行清理逻辑或直接结束任务。

如何监控生产环境中的锁竞争?
利用 lock.getQueueLength() 和日志定期采集,或结合 APM 工具监控线程等待情况。

扩展方向与性能优化

Condition 多队列
使用多个 Condition 实现更精细的等待/唤醒控制,例如生产者—消费者的 notFull / notEmpty

锁分段
对大数据结构进行分段加锁(类似 ConcurrentHashMap),降低锁粒度提升并发度。

公平性调优
在高并发场景下考虑非公平锁与超时重试结合,避免严格公平带来的吞吐下降。

锁剥离
当只有读操作时,可使用 ReadWriteLock 切换到无阻塞读锁,提高并发读性能。

可视化诊断
集成到监控平台或定制 Web 界面,实时展示锁争用、队列长度和线程等待图。

以上就是Java使用重入锁实现线程同步的示例代码的详细内容,更多关于Java重入锁线程同步的资料请关注脚本之家其它相关文章!

相关文章

  • 解析Java Class 文件过程

    解析Java Class 文件过程

    class文件全名称为Java class文件,主要在平台无关性和网络移动性方面使Java更适合网络。它在平台无关性方面的任务是:为Java程序提供独立于底层主机平台的二进制形式的服务。下面我们来详细解读下它吧
    2019-05-05
  • 详解java爬虫jsoup解析多空格class数据

    详解java爬虫jsoup解析多空格class数据

    在本篇内容中小编给大家分享了java爬虫jsoup怎么解析多空格class数据的方法和技巧,需要的朋友们跟着学习下。
    2018-12-12
  • Java实现图片转换PDF文件的示例代码

    Java实现图片转换PDF文件的示例代码

    这篇文章主要介绍了Java实现图片转换PDF文件的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Java字符编码原理(动力节点Java学院整理)

    Java字符编码原理(动力节点Java学院整理)

    Java开发中,常常会遇到乱码的问题,一旦遇到这种问题,常常比较烦恼,大家都不想承认是自己的代码问题,其实搞明白编码的本质过程就简单多了,接下来小编给大家带来java字符编码原理,要求看看吧
    2017-04-04
  • SpringBoot+Mybatis项目使用Redis做Mybatis的二级缓存的方法

    SpringBoot+Mybatis项目使用Redis做Mybatis的二级缓存的方法

    本篇文章主要介绍了SpringBoot+Mybatis项目使用Redis做Mybatis的二级缓存的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12
  • 使用IDEA直接连接MySQL数据库的方法

    使用IDEA直接连接MySQL数据库的方法

    这篇文章主要介绍了如何使用IDEA直接连接MySQL数据库,首先需要新建一个空项目,第一次连接 需要先下载驱动,文中给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-04-04
  • Java PhantomJs完成html图片输出功能

    Java PhantomJs完成html图片输出功能

    给大家带来一篇关于用Java PhantomJs完成html图片输出功能的教学内容,有兴趣的朋友学习参考下吧。
    2017-12-12
  • java解析xml之jdom解析xml示例分享

    java解析xml之jdom解析xml示例分享

    JDOM是专门为Java打造的API,JDOM采用了Java中的Collection架构来封装集合,是Java爱好者更加熟悉的模式,下面看使用示例
    2014-01-01
  • Java正则表达式的替换和分组功能

    Java正则表达式的替换和分组功能

    这篇文章主要给大家介绍了关于Java正则表达式的替换和分组功能的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • 后端java压缩图片超详细图文教程

    后端java压缩图片超详细图文教程

    这篇文章主要给大家介绍了关于后端java压缩图片的相关资料,片压缩是一种广泛采用的技术,它不仅能显著减小文件大小,释放更多存储空间,还能提升图片加载速度,避免长时间等待,需要的朋友可以参考下
    2024-04-04

最新评论