面试题答案
一键面试- 定义线程安全队列类:
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ThreadSafeQueue<T> { private Object[] queue; private int head; private int tail; private int capacity; private final Lock lock = new ReentrantLock(); private final Condition notEmpty = lock.newCondition(); private final Condition notFull = lock.newCondition(); public ThreadSafeQueue(int initialCapacity) { this.capacity = initialCapacity; this.queue = new Object[capacity]; this.head = 0; this.tail = 0; } public void enqueue(T item) throws InterruptedException { lock.lock(); try { while ((tail + 1) % capacity == head) { notFull.await(); } queue[tail] = item; tail = (tail + 1) % capacity; notEmpty.signal(); } finally { lock.unlock(); } } public T dequeue() throws InterruptedException { lock.lock(); try { while (head == tail) { notEmpty.await(); } T item = (T) queue[head]; head = (head + 1) % capacity; notFull.signal(); return item; } finally { lock.unlock(); } } }
- 锁机制和并发控制:
- 锁机制:使用
ReentrantLock
来保证对队列的操作是线程安全的。ReentrantLock
提供了比synchronized
关键字更灵活的锁控制。 - 并发控制:
- Condition对象:使用
Condition
对象(notEmpty
和notFull
)来实现线程的等待和唤醒。当队列为空时,dequeue
操作的线程会在notEmpty
上等待,当有新元素入队时,enqueue
操作会唤醒等待在notEmpty
上的线程。同理,当队列满时,enqueue
操作的线程会在notFull
上等待,当有元素出队时,dequeue
操作会唤醒等待在notFull
上的线程。
- Condition对象:使用
- 锁机制:使用
- 性能优化:
- 减少锁的粒度:这里通过
Condition
对象来精确控制线程的等待和唤醒,避免了不必要的锁竞争。比如,只有当队列满或空时,线程才会等待,而不是每次操作都等待。 - 使用高效的数据结构:这里采用数组作为底层数据结构,在入队和出队操作上时间复杂度为O(1),相比于链表等数据结构,在性能上有一定优势。
- 减少锁的粒度:这里通过
- 可扩展性:
- 动态扩容:可以在
enqueue
方法中添加动态扩容逻辑,当队列满时,创建一个更大的数组,并将原数组中的元素复制过去,同时更新capacity
、head
和tail
等参数。 - 多生产者 - 多消费者模式:当前实现支持多个线程同时进行入队和出队操作,并且通过
Condition
对象来协调多个生产者和消费者之间的关系,具有较好的可扩展性。例如,在高并发场景下,多个生产者线程可以同时尝试入队,多个消费者线程可以同时尝试出队。
- 动态扩容:可以在