Java使用重入锁实现线程同步的示例代码
作者:Katie。
一、项目背景详细介绍
在多线程环境中,为保证对共享资源的安全访问,常用的同步手段是 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 中给出代码调用示例及注意事项;
- 对比
synchronized与ReentrantLock的使用差异。
三、相关技术详细介绍
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启动多个线程; - 使用
CountDownLatch或CyclicBarrier协调线程启动同步测试; - 记录锁获取次数与失败次数进行统计。
四、实现思路详细介绍
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 notEmpty、notFull并在方法中配合使用。
公平与非公平锁对比
- 在测试中构造两种
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()等方法便于在运行时诊断锁状态。
项目常见问题及解答
为何要在 finally 中 unlock()?
避免在执行过程中抛出异常导致锁未释放,进而引发死锁。
tryLock 获不到锁后还能重试吗?
可以在代码中判断失败后循环调用,或结合退避机制重试。
公平锁性能更差吗?
是的,公平锁会增加上下文切换成本,一般在需要严格顺序时使用,否则推荐默认非公平锁。
lockInterruptibly 如何正确处理中断?
调用方法需声明 throws InterruptedException,在捕获后可执行清理逻辑或直接结束任务。
如何监控生产环境中的锁竞争?
利用 lock.getQueueLength() 和日志定期采集,或结合 APM 工具监控线程等待情况。
扩展方向与性能优化
Condition 多队列
使用多个 Condition 实现更精细的等待/唤醒控制,例如生产者—消费者的 notFull / notEmpty。
锁分段
对大数据结构进行分段加锁(类似 ConcurrentHashMap),降低锁粒度提升并发度。
公平性调优
在高并发场景下考虑非公平锁与超时重试结合,避免严格公平带来的吞吐下降。
锁剥离
当只有读操作时,可使用 ReadWriteLock 切换到无阻塞读锁,提高并发读性能。
可视化诊断
集成到监控平台或定制 Web 界面,实时展示锁争用、队列长度和线程等待图。
以上就是Java使用重入锁实现线程同步的示例代码的详细内容,更多关于Java重入锁线程同步的资料请关注脚本之家其它相关文章!
