Java中的ReentrantLock详解
在Java编程中,多线程同步是一个常见的需求。为了保证多个线程对共享资源的安全访问,Java提供了多种锁机制,其中ReentrantLock
是一个重要的工具。本文将详细介绍ReentrantLock
的使用,包括其基本操作、与synchronized
的对比、条件变量的使用等。
1. ReentrantLock的基本使用
ReentrantLock
是一个可重入锁,意味着一个线程可以多次获取同一把锁而不会被阻塞。以下是一个简单的示例:
package cn.juwatech.lock;
import java.util.concurrent.locks.ReentrantLock;
public class BasicUsage {
private final ReentrantLock lock = new ReentrantLock();
private int counter = 0;
public void increment() {
lock.lock();
try {
counter++;
} finally {
lock.unlock();
}
}
public int getCounter() {
return counter;
}
public static void main(String[] args) throws InterruptedException {
BasicUsage usage = new BasicUsage();
Runnable task = usage::increment;
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final counter: " + usage.getCounter());
}
}
在这个示例中,lock.lock()
用于获取锁,lock.unlock()
用于释放锁。在try
块中进行实际操作,以确保无论操作是否抛出异常,锁都能被释放。
2. 与synchronized的对比
ReentrantLock
和synchronized
关键字都可以用于实现线程同步,但它们有一些不同之处:
- 功能丰富:
ReentrantLock
提供了比synchronized
更多的功能,如定时锁、可中断锁等。 - 性能:在高竞争情况下,
ReentrantLock
通常比synchronized
性能更好。 - 灵活性:
ReentrantLock
可以更灵活地控制锁的获取和释放。
以下是一个示例,展示了定时锁和可中断锁的使用:
package cn.juwatech.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class AdvancedUsage {
private final ReentrantLock lock = new ReentrantLock();
public void timedLock() {
try {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println("Lock acquired");
} finally {
lock.unlock();
}
} else {
System.out.println("Could not acquire lock");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void interruptibleLock() {
try {
lock.lockInterruptibly();
try {
System.out.println("Lock acquired");
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
AdvancedUsage usage = new AdvancedUsage();
Thread t1 = new Thread(usage::timedLock);
Thread t2 = new Thread(usage::interruptibleLock);
t1.start();
t2.start();
}
}
3. 使用Condition实现更复杂的同步
ReentrantLock
提供了Condition
对象来实现更加复杂的线程同步。Condition
类似于传统的Object
监视器方法(wait
、notify
和notifyAll
),但功能更强大和灵活。
以下是一个使用Condition
实现生产者-消费者模式的示例:
package cn.juwatech.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.LinkedList;
import java.util.Queue;
public class ProducerConsumer {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Queue<Integer> queue = new LinkedList<>();
private final int MAX_SIZE = 10;
public void produce(int value) {
lock.lock();
try {
while (queue.size() == MAX_SIZE) {
notFull.await();
}
queue.add(value);
System.out.println("Produced: " + value);
notEmpty.signalAll();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public void consume() {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
int value = queue.poll();
System.out.println("Consumed: " + value);
notFull.signalAll();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
Runnable producerTask = () -> {
for (int i = 0; i < 20; i++) {
pc.produce(i);
}
};
Runnable consumerTask = () -> {
for (int i = 0; i < 20; i++) {
pc.consume();
}
};
Thread producerThread = new Thread(producerTask);
Thread consumerThread = new Thread(consumerTask);
producerThread.start();
consumerThread.start();
}
}
在这个示例中,我们使用了两个Condition
对象:notFull
和notEmpty
,分别用于控制队列的满和空状态。生产者在队列满时等待,在生产新数据后通知消费者;消费者在队列空时等待,在消费数据后通知生产者。
4. 公平锁与非公平锁
ReentrantLock
可以通过构造函数参数指定为公平锁或非公平锁。公平锁保证锁的获取顺序是按照请求顺序,而非公平锁则允许抢占。
package cn.juwatech.lock;
import java.util.concurrent.locks.ReentrantLock;
public class FairLockUsage {
private final ReentrantLock fairLock = new ReentrantLock(true);
public void accessResource() {
fairLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " acquired the lock");
} finally {
fairLock.unlock();
}
}
public static void main(String[] args) {
FairLockUsage usage = new FairLockUsage();
Runnable task = usage::accessResource;
for (int i = 0; i < 5; i++) {
Thread t = new Thread(task);
t.start();
}
}
}
在这个示例中,我们创建了一个公平锁,通过传递true
给ReentrantLock
的构造函数来实现。
总结
通过上述示例,我们详细介绍了ReentrantLock
的各种功能和用法,包括基本使用、与synchronized
的对比、条件变量的使用以及公平锁和非公平锁的区别。ReentrantLock
提供了比synchronized
更灵活和强大的锁机制,是Java并发编程中不可或缺的工具。