面试题答案
一键面试BlockingQueue保证线程安全的方式
- 使用锁机制:BlockingQueue 通常会使用
ReentrantLock
等锁来保证对队列数据结构的访问是线程安全的。锁可以确保同一时间只有一个线程能够对队列进行操作,避免多个线程同时修改队列状态导致的数据不一致问题。 - 条件变量(Condition):配合锁机制,使用
Condition
来实现线程的等待和唤醒。当队列满时,尝试添加元素的线程会等待在对应的条件变量上;当队列空时,尝试获取元素的线程也会等待在相应条件变量上。当队列状态改变(如队列有空间或有元素)时,会唤醒等待在条件变量上的线程。
以ArrayBlockingQueue为例
- 锁的使用:
ArrayBlockingQueue
内部使用ReentrantLock
来保证线程安全。例如,在put
方法(向队列添加元素)和take
方法(从队列获取元素)中,首先会获取锁:
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();
}
}
这里通过 lock.lockInterruptibly()
获取锁,保证在获取锁的过程中线程可以被中断。
2. 条件变量的使用:ArrayBlockingQueue
有两个 Condition
对象,notEmpty
和 notFull
。notFull
用于队列满时,添加元素的线程等待;notEmpty
用于队列空时,获取元素的线程等待。如上述 put
方法中,当 count == items.length
(队列满)时,线程调用 notFull.await()
进入等待状态,直到其他线程从队列取出元素后唤醒等待在 notFull
上的线程。在 take
方法中类似:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
当 count == 0
(队列空)时,线程调用 notEmpty.await()
等待,直到其他线程向队列添加元素后唤醒等待在 notEmpty
上的线程。通过这种锁和条件变量的配合使用,ArrayBlockingQueue
实现了线程安全的阻塞队列功能。