ReentrantLock
是 Java 里用于实现线程同步的工具,它在 java.util.concurrent.locks
包中,是 Lock
接口的一个具体实现。和 synchronized
关键字相比,ReentrantLock
更灵活、功能更丰富。下面为你详细介绍它的使用方法:
1. 基本使用
基本使用步骤如下:
- 创建
ReentrantLock
实例。 - 在需要同步的代码块前调用
lock()
方法获取锁。 - 同步代码块执行完毕后,调用
unlock()
方法释放锁。
以下是示例代码:
java
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
System.out.println(Thread.currentThread().getName() + " 增加后的值: " + count);
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
example.increment();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
example.increment();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
}
在这个示例中,increment
方法借助 ReentrantLock
保证线程安全。lock.lock()
用来获取锁,lock.unlock()
用来释放锁。为确保锁一定会被释放,unlock()
方法要放在 finally
块中。
2. 可重入性
ReentrantLock
具备可重入性,这意味着同一个线程能够多次获取同一把锁,而不会产生死锁。下面是示例代码:
java
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantExample {
private final ReentrantLock lock = new ReentrantLock();
public void outerMethod() {
lock.lock();
try {
System.out.println("外层方法获取锁");
innerMethod();
} finally {
lock.unlock();
}
}
public void innerMethod() {
lock.lock();
try {
System.out.println("内层方法获取锁");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantExample example = new ReentrantExample();
example.outerMethod();
}
}
在这个示例中,outerMethod
方法获取锁之后,调用了 innerMethod
方法,innerMethod
方法又一次获取了同一把锁,这就是可重入性的体现。
3. 公平锁
ReentrantLock
可以创建公平锁,公平锁会按照线程请求锁的顺序来分配锁。创建公平锁时,需要在构造函数中传入 true
。示例代码如下:
java
import java.util.concurrent.locks.ReentrantLock;
public class FairLockExample {
private final ReentrantLock fairLock = new ReentrantLock(true);
public void doSomething() {
fairLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取到公平锁");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
fairLock.unlock();
}
}
public static void main(String[] args) {
FairLockExample example = new FairLockExample();
for (int i = 0; i < 5; i++) {
new Thread(() -> example.doSomething()).start();
}
}
}
在这个示例中,通过 new ReentrantLock(true)
创建了公平锁,线程会按照请求锁的顺序依次获取锁。
4. 尝试获取锁
ReentrantLock
提供了 tryLock()
方法,该方法可尝试获取锁,若锁可用则获取并返回 true
,若不可用则立即返回 false
,不会阻塞线程。示例代码如下:
java
import java.util.concurrent.locks.ReentrantLock;
public class TryLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void tryLockMethod() {
if (lock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + " 获取到锁");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " 未能获取到锁");
}
}
public static void main(String[] args) {
TryLockExample example = new TryLockExample();
Thread t1 = new Thread(() -> example.tryLockMethod());
Thread t2 = new Thread(() -> example.tryLockMethod());
t1.start();
t2.start();
}
}
在这个示例中,tryLock()
方法会尝试获取锁,若获取失败则会执行 else
块中的代码。
5. 带超时的尝试获取锁
ReentrantLock
还提供了 tryLock(long timeout, TimeUnit unit)
方法,该方法可以在指定的时间内尝试获取锁,若在超时时间内获取到锁则返回 true
,否则返回 false
。示例代码如下:
java
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TryLockWithTimeoutExample {
private final ReentrantLock lock = new ReentrantLock();
public void tryLockWithTimeout() {
try {
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) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TryLockWithTimeoutExample example = new TryLockWithTimeoutExample();
Thread t1 = new Thread(() -> example.tryLockWithTimeout());
Thread t2 = new Thread(() -> example.tryLockWithTimeout());
t1.start();
t2.start();
}
}
在这个示例中,tryLock(1, TimeUnit.SECONDS)
方法会在 1 秒内尝试获取锁,若 1 秒内未获取到锁则返回 false
。
在 Java 中,ReentrantLock
类的 newCondition()
方法用于创建一个 Condition
对象。Condition
接口为线程提供了一种更灵活的等待/通知机制,它可以替代传统的 Object
类的 wait()
、notify()
和 notifyAll()
方法。下面详细介绍 newCondition()
的用途和使用方式。
用途
- 线程协作 :
Condition
允许线程在某个条件不满足时等待,当条件满足时被其他线程唤醒。这有助于实现线程之间的协作,避免了传统wait()
和notify()
方法的一些局限性,例如可以创建多个等待队列。 - 精确控制 :可以为不同的条件创建不同的
Condition
对象,实现更精确的线程唤醒和等待控制。比如,一个生产者 - 消费者模型中,可以有一个Condition
用于生产者等待缓冲区有空间,另一个Condition
用于消费者等待缓冲区有元素。
使用示例
下面通过一个简单的生产者 - 消费者模型来展示 newCondition()
的使用:
java
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class ProducerConsumer {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity = 5;
private final ReentrantLock lock = new ReentrantLock();
// 用于生产者等待的条件
private final Condition notFull = lock.newCondition();
// 用于消费者等待的条件
private final Condition notEmpty = lock.newCondition();
public void produce() throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
// 缓冲区满,生产者等待
notFull.await();
}
int item = (int) (Math.random() * 100);
queue.add(item);
System.out.println("Produced: " + item);
// 通知消费者缓冲区有元素了
notEmpty.signal();
} finally {
lock.unlock();
}
}
public void consume() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
// 缓冲区空,消费者等待
notEmpty.await();
}
int item = queue.poll();
System.out.println("Consumed: " + item);
// 通知生产者缓冲区有空间了
notFull.signal();
} finally {
lock.unlock();
}
}
}
public class Main {
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
Thread producer = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
pc.produce();
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread consumer = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
pc.consume();
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
consumer.start();
}
}
代码解释
-
创建
Condition
对象:private final Condition notFull = lock.newCondition();
:创建一个Condition
对象notFull
,用于表示缓冲区不满的条件。private final Condition notEmpty = lock.newCondition();
:创建一个Condition
对象notEmpty
,用于表示缓冲区不为空的条件。
-
生产者逻辑:
- 在
produce()
方法中,当缓冲区满时,调用notFull.await()
使生产者线程等待。 - 当生产一个元素后,调用
notEmpty.signal()
唤醒一个等待的消费者线程。
- 在
-
消费者逻辑:
- 在
consume()
方法中,当缓冲区为空时,调用notEmpty.await()
使消费者线程等待。 - 当消费一个元素后,调用
notFull.signal()
唤醒一个等待的生产者线程。
- 在
通过这种方式,Condition
对象实现了生产者和消费者线程之间的精确协作。