ReentrantLock (可重入锁)
ReentrantLock实现了Lock接口, 内部通过继承AQS, 实现了一个同步器. 可以通过同步器来创建Condition条件变量, 可以用作容器, 存放不同条件的等待线程.
说明ReentrantLock与AQS的关系 类图:
相对于synchronized, 都支持可重入. 它还具备如下特点:
- 可重入性:与synchronized关键字一样,ReentrantLock是可重入的,即同一个线程可以多次获取同一个锁而不会造成死锁。
- 公平性选择:ReentrantLock提供了公平锁和非公平锁的选择。公平锁会按照线程的请求顺序来获取锁,而非公平锁则不保证公平性,可能导致某些线程长时间等待。
- 锁获取和释放:使用ReentrantLock可以通过
lock()
方法获取锁,并通过unlock()
方法释放锁。需要注意的是,获取锁后一定要在合适的地方释放锁,通常使用finally
块来确保锁的释放。 - 条件变量:ReentrantLock提供了条件变量(Condition)的支持,通过
newCondition()
方法创建条件变量。条件变量可以让线程在特定的条件下等待或被唤醒,常与锁配合使用。 - 中断响应:ReentrantLock支持对线程的中断响应。在等待锁的过程中,可以通过调用
lockInterruptibly()
方法使线程对中断作出响应。 - 锁的可见性:与synchronized关键字一样,ReentrantLock同样保证了锁的可见性,即当一个线程释放锁时,对应的变量修改将对其他线程可见。
lockInterruptibly() :
是一个可以被打断的获取锁操作, 相当于是支持interrupt()方法的lock()方法. 普通的lock()方法如果被其他线程阻塞, 是无法通过interrupt()方法打断的, lockInterruptibly()方法, 是在加锁的时候如果被阻塞, 允许被打断.
Condition :
Condition是条件变量对象, 相当于是一个存放等待线程的容器, 比如在线程池中, 就会使用到Condition, 来存放不同的工作线程.
阻塞方法: await() 相当于 object对象的wait()
唤醒方法: signal() 相当于 object对象的 notify()
使用方法: 使用condition
之前, 必须调用ReentrantLock
对象的lock方法
, 之后调用condition.await()
方法, 就把该线程加到condition
对象中等待了, 后续执行signal()
方法, 就会从condition
容器中随机唤醒一个线程, 继续执行
线程池是如何使用Condition的?
线程池没有直接使用Condition, 而是通过缓冲队列来使用Condition, 让线程没有任务执行时, 进入wait状态, 当有任务时, 会被唤醒执行任务.
例如, 在ArrayBlockingQueue队列中, 维护了两个condition, 一个是notEmpty, 当线程池调用队列的take方法获取任务执行的时候, 队列会判断当前队列中是否存在等待中的任务, 如果存在, 则取出执行, 如果没有, 则进入notEmpty Condition等待. 源码如下
线程池从队列获取任务, 队列中没任务, 则线程进入notEmpty等待
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 队列中任务数量为0
while (count == 0)
// 进入notEmpty等待
notEmpty.await();
// 如果不为0 , 取出执行任务
return dequeue();
} finally {
lock.unlock();
}
}
线程往队列中加入任务时, 如果队列满了, 则进入notFull中等待
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
notEmpty
条件:用于控制任务队列中是否有任务可执行的条件。当任务队列为空时,线程池的工作线程会等待在notEmpty
条件上,直到有新的任务被提交到任务队列中,此时会调用notEmpty.signal()
方法来通知等待的工作线程继续执行任务。notFull
条件:用于控制任务队列中是否还有空间可以接受新的任务的条件。当任务队列满时,如果线程池的最大线程数未达到限制,新提交的任务会直接创建新的工作线程来执行。当任务队列中有空闲位置时,线程池的工作线程会等待在notFull
条件上,直到有新的任务被提交或者有工作线程被空闲释放,此时会调用notFull.signal()
方法来通知等待的工作线程继续执行任务。