Java并发编程专题(五)——详解(JUC)ReentrantLock
ReentrantLock是java.util.concurrent(J.U.C)包中的一个锁工具类,也是Java多线程中常用的互斥锁。它可用于代替synchronized关键字进行线程同步,比synchronized更灵活。
1. 使用ReentrantLock
1.1 创建ReentrantLock
要使用ReentrantLock,我们需要通过以下代码创建一个实例:
ReentrantLock lock = new ReentrantLock();
1.2 加锁和解锁
使用ReentrantLock进行线程同步,需要加锁和解锁。
加锁:
lock.lock();
try {
// 要同步的代码块
} finally {
lock.unlock();
}
解锁:
使用try/finally块进行加锁和解锁操作是很好的方式,如果加锁的代码块中抛出了异常,finally中的代码也能够得到执行,可以确保锁在任何情况下都会被释放。
1.3 非阻塞加锁
ReentrantLock还提供了非阻塞的加锁方法tryLock(),该方法会立即返回,如果成功获取锁则返回true,否则返回false。使用该方法可以避免线程的一直等待。
if(lock.tryLock()){
try {
//需要同步的代码块
} finally {
lock.unlock();
}
}else{
//其他处理代码
}
1.4 可中断的加锁
当多个线程正在等待一个锁释放时,可以使用可中断的加锁方式,以避免一个线程一直等待锁。我们可以在等待锁的过程中中断线程。如果调用线程还没有持有锁并且中断任务,那么将抛出一个InterruptedException异常。
try {
lock.lockInterruptibly();
try {
// 线程中断之后执行这里的代码
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
// 处理中断的代码
}
2. ReentrantLock的高级应用
2.1 公平锁和非公平锁
在创建ReentrantLock实例的时候,可以指定该锁是否是公平的,默认情况下是非公平的。
公平锁:指多个线程等待锁时,获取锁的顺序与等待的顺序相同
非公平锁:指多个线程等待锁时,先请求锁的线程不一定先获得锁,优先级高的线程先获得锁
公平锁会降低系统的整体吞吐量,因为它会引起线程上下文切换,但公平锁会避免饥饿问题,保证每个线程都有机会获得锁。
使用ReentrantLock的构造函数可以创建一个公平的锁:
ReentrantLock lock = new ReentrantLock(true);
2.2 条件
ReentrantLock还可以通过Condition接口实现类似synchronized中的wait()和notify()的功能。
一个锁对象可以有一个或多个条件对象,每个条件对象都维护了一个等待队列。在使用条件前,需要通过ReentrantLock的newCondition()方法创建一个条件对象:
Condition condition = lock.newCondition();
然后可以使用await()和signal()方法在条件上等待和唤醒线程。
- 阻塞等待
condition.await();
该方法只能在锁保护下调用,否则会抛出IllegalMonitorStateException异常,方法会释放锁,并将当前线程放到条件的等待队列中。
- 唤醒
condition.signal();
该方法可以唤醒在该条件队列中等待的一个线程。
下面是一个示例代码,使用ReentrantLock和条件来实现生产者和消费者的示例:
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerConsumer {
private Queue<Integer> queue = new LinkedList<>();
private int capacity = 10;
private ReentrantLock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public void put(int num) throws InterruptedException{
lock.lock();
try{
while(queue.size() == capacity) {
notFull.await();
}
queue.offer(num);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public int take() throws InterruptedException{
lock.lock();
try{
while(queue.isEmpty()) {
notEmpty.await();
}
int num = queue.poll();
notFull.signal();
return num;
} finally {
lock.unlock();
}
}
public static void main(String[] args){
ProducerConsumer pc = new ProducerConsumer();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0; i<100; i++){
try {
pc.put(i);
System.out.println("生产:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0; i<100; i++){
try {
int num = pc.take();
System.out.println("消费:" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
t2.start();
}
}
3. 总结
以上就是ReentrantLock的基本用法和一些高级用法,ReentrantLock比synchronized更灵活,但是在使用方面也需要注意一些问题,例如避免死锁。在多线程编程中,还需要综合考虑线程的效率和安全,以达到最佳的线程同步效果。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:java并发编程专题(五)—-详解(JUC)ReentrantLock - Python技术站