ReentrantLock 使用详解
1、简介
ReentrantLock是一种可重入的独占锁,它允许同一个线程多次获取同一个锁而不会被阻塞。它的功能类似于synchronized是一种互斥锁,可以保证线程安全。相对于 synchronized,
ReentrantLock具备如下特点:
- 可中断。
- 可以设置超时时间。
- 可以设置为公平锁。
- 支持多个条件变量。
2、常用API介绍
- void lock() : 获取锁,调用该方法当前线程会获取锁,当锁获得后,该方法返回。
- void lockInterruptibly() throws InterruptedException: 在锁的获取中可以中断当前线程。
- boolean tryLock(): 尝试非阻塞的获取锁,调用该方法后立即返回。如果能够获取到返回true,否则返回false。
- boolean tryLock(long time, TimeUnit unit) throws InterruptedException: 超时获取锁,当前线程在以下三种情况下会被返回:1、当前线程在超时时间内获取了锁。2、当前线程在超时时间内被中断。3、超时时间结束,返回false。
- void unlock(): 释放锁。
- Condition newCondition(): 获取等待通知组件,该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的await()方法,而调用后,当前线程将释放锁。
使用示例:
private final Lock lock = new ReentrantLock();
public void foo() {
// 获取锁
lock.lock();
try {
// 程序执行逻辑
} finally {
// finally语句块可以确保lock被正确释放
lock.unlock();
}
}
在使用时要注意 4 个问题:
- 1.默认情况下 ReentrantLock 为非公平锁而非公平锁;
- 2.加锁次数和释放锁次数一定要保持一致,否则会导致线程阻塞或程序异常;
- 3.加锁操作一定要放在 try 代码之前,这样可以避免未加锁成功又释放锁的异常;
- 4.释放锁一定要放在 finally 中,否则会导致线程阻塞。
公平锁和非公平锁
ReentrantLock支持公平锁和非公平锁两种模式:
- 公平锁:线程在获取锁时,按照等待的先后顺序获取锁。
- 非公平锁:线程在获取锁时,不按照等待的先后顺序获取锁,而是随机获取锁。ReentrantLock默认是非公平锁
ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁
ReentrantLock lock = new ReentrantLock(true); //公平锁
工作原理
当有线程调用lock方法的时候: 如果线程获取到锁了,那么就会通过CAS的方式把AQS内部的state设置成为1。这个时候,当前线程就获取到锁了。只有首部的节点(head节点封装的线程)可以获取到锁。其他线程都会加入到这一个阻塞队列当中。如果是公平锁的话,当head节点释放锁之后,会优先唤醒head.next这一个节点对应的线程。如果是非公平锁,允许新来的线程和head之后唤醒的线程通过cas竞争锁。
可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。在实际开发中,可重入锁常常应用于递归操作、调用同一个类中的其他方法、锁嵌套等场景中。
3、Condition详解
在Java中,Condition是一个接口,它提供了线程之间的协调机制,可以将它看作是一个更加灵活、更加强大的wait()和notify()机制,通常与Lock接口(比如ReentrantLock)一起使用。它的核心作用体现在两个方面,如下:
等待/通知机制:它允许线程等待某个条件成立,或者通知其他线程某个条件已经满足,这与使用Object的wait()和notify()方法相似,但Condition提供了更高的灵活性和更多的控制。
多条件协调:与每个Object只有一个内置的等待/通知机制不同,一个Lock可以对应多个Condition对象,这意味着可以为不同的等待条件创建不同的Condition,从而实现对多个等待线程集合的独立控制。
方法介绍:
- void await():使当前线程等待,直到被其他线程通过 signal() 或 signalAll() 方法唤醒,或者线程被中断,或者发生了其他不可预知的情况(如假唤醒)。该方法会在等待之前释放当前线程所持有的锁,在被唤醒后会再次尝试获取锁。
- boolean await(long time, TimeUnit unit):使当前线程等待指定的时间,或者直到被其他线程通过 signal() 或 signalAll() 方法唤醒,或者线程被中断。如果在指定的时间内没有被唤醒,该方法将返回。在等待之前会释放当前线程所持有的锁,在被唤醒或超时后会再次尝试获取锁。
- void signal():唤醒等待在此 Condition 上的一个线程。如果有多个线程正在等待,则选择其中的一个进行唤醒。被唤醒的线程将从其 await() 调用中返回,并重新尝试获取与此 Condition 关联的锁。
- void signalAll():唤醒等待在此 Condition 上的所有线程。每个被唤醒的线程都将从其 await() 调用中返回,并重新尝试获取与此 Condition 关联的锁。
java.util.concurrent类库中提供Condition类来实现线程之间的协调。调用Condition.await() 方法使线程等待,其他线程调用Condition.signal() 或 Condition.signalAll() 方法唤醒等待的线程。
注意:调用Condition的await()和signal()方法,都必须在lock保护之内。
案例:实现简单的生产者、消费者模型
public class MyQueue {
private Object[] items;
int size = 0;
int takeIndex;
int putIndex;
private ReentrantLock lock;
public Condition notEmpty; //消费者线程阻塞唤醒条件,队列为空阻塞,生产者生产完唤醒
public Condition notFull; //生产者线程阻塞唤醒条件,队列满了阻塞,消费者消费完唤醒
public MyQueue(int capacity) {
this.items = new Object[capacity];
lock = new ReentrantLock();
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
public void put(Object value) throws Exception {
//加锁
lock.lock();
try {
while (size == items.length)
// 队列满了让生产者等待
notFull.await();
items[putIndex] = value;
if (++putIndex == items.length)
putIndex = 0;
size++;
// 生产完唤醒消费者
notEmpty.signal();
} finally {
System.out.println("producer生产:" + value);
//解锁
lock.unlock();
}
}
public Object take() throws Exception {
lock.lock();
try {
// 队列空了就让消费者等待
while (size == 0)
notEmpty.await();
Object value = items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
size--;
notFull.signal();
//消费完唤醒生产者生产
return value;
} finally {
lock.unlock();
}
}
static class Producer implements Runnable {
private MyQueue queue;
public Producer(MyQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
// 隔1秒轮询生产一次
while (true) {
Thread.sleep(1000);
queue.put(new Random().nextInt(1000));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
static class Customer implements Runnable {
private MyQueue queue;
public Customer(MyQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
// 隔2秒轮询消费一次
while (true) {
Thread.sleep(2000);
System.out.println("consumer消费:" + queue.take());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建队列
MyQueue queue = new MyQueue(5);
//启动生产者线程
new Thread(new Producer(queue)).start();
// 启动消费者线程
new Thread(new Customer(queue)).start();
}
}

浙公网安备 33010602011771号