【面试题】并发篇

【面试题】并发篇
時光内容来源于网络,如有侵权请联系删除
什么是 Java 的 StampedLock?
StampedLock 是 Java 8 引入的一个锁机制,与传统的 ReentrantLock 和 ReadWriteLock 相比,StampedLock 通过引入乐观读锁和时间戳(stamp)的概念,提升了读写性能,尤其是在读多写少的场景下。
核心特性:
- 写锁(write lock):独占模式的锁,和
ReentrantLock类似,保证写操作的排他性。 - 悲观读锁(read lock):共享模式的锁,多个线程可以同时持有读锁,但写锁需要等待。
- 乐观读锁(optimistic read lock):无需阻塞的读锁机制,允许在没有竞争的情况下进行快速读取。当检测到有写操作发生时,才会回退到悲观读锁或重试。
时间戳(stamp):
- 每个锁操作都会返回一个 stamp,代表了锁的状态,后续操作需要根据这个 stamp 来验证锁是否有效。
StampedLock 的优缺点
优点:
- 乐观读模式提供了更高效的并发读操作,避免了传统锁机制下的线程阻塞。
- 更灵活的锁机制,允许不同的锁模式进行切换,适合不同场景。
缺点:
- 不可重入:
StampedLock是非重入锁,这意味着同一个线程不能在持有锁时再次获取同类型的锁,否则会造成死锁。 - 读锁饥饿:在高写入负载的场景下,悲观读锁可能会被长期阻塞,导致读操作饥饿。
- CPU飙升风险:如果线程使用
writeLock()或者readLock()获得锁之后,线程还没执行完就被interrupt()的话,会导致CPU飙升,需要用readLockInterruptibly或者writeLockInterruptibly。
我本地跑了下复现了:
内部原理
StampedLock通过一个长整型值来管理状态,低位用于表示锁的类型(写锁、读锁),高位用于表示锁的计数。当乐观读锁被获取时,生成一个时间戳(stamp),并在后续验证时判断是否有写操作发生。validate(stamp)方法可以有效判断在持有乐观读锁的情况下,是否有其他写操作干扰(被修改了),从而决定是否要变为悲观读锁。
代码示例:
1 | import java.util.concurrent.locks.StampedLock; |
关键点:
- 写锁用于修改共享资源,多个线程无法同时持有写锁。
- 悲观读锁保证数据一致性,适合写操作较多的场景。
- 乐观读锁适用于读多写少的场景,可以在大部分时间无锁读取,大大提高性能。
你使用过 Java 中的哪些阻塞队列?
阻塞队列主要用来阻塞队列的插入和获取操作,当队列满了的时候插入操作会被阻塞,直到队列有空位。当队列为空的时候获取操作会被阻塞,直到队列有值。
常用在实现生产者和消费者场景,在笔试题中比较常见。
常见的阻塞队列包括:
- ArrayBlockingQueue:一个有界队列,底层基于数组实现。需要在初始化时指定队列的大小,队列满时,生产者会被阻塞,队列空时,消费者会被阻塞。
- LinkedBlockingQueue:基于链表的阻塞队列,允许可选的界限(有界或无界)。无界模式下可以不断添加元素,直到耗尽系统资源。有界模式则类似于
ArrayBlockingQueue,但吞吐量通常较高。 - PriorityBlockingQueue:一个无界的优先级队列,元素按照自然顺序或者指定的比较器顺序进行排序。与其他阻塞队列不同的是,PriorityBlockingQueue 不保证元素的 FIFO 顺序。
- DelayQueue:一个无界队列,队列中的元素必须实现
Delayed接口,只有当元素的延迟时间到期时,才能被取出。常用于延迟任务调度。 - SynchronousQueue:一个没有内部容量的队列,每个插入操作必须等待对应的移除操作,反之亦然。常用于在线程之间的直接传递任务,而不是存储任务。
回答重点
阻塞队列主要用来阻塞队列的插入和获取操作,当队列满了的时候插入操作会被阻塞,直到队列有空位。当队列为空的时候获取操作会被阻塞,直到队列有值。
常用在实现生产者和消费者场景,在笔试题中比较常见。
常见的阻塞队列包括:
- ArrayBlockingQueue:一个有界队列,底层基于数组实现。需要在初始化时指定队列的大小,队列满时,生产者会被阻塞,队列空时,消费者会被阻塞。
- LinkedBlockingQueue:基于链表的阻塞队列,允许可选的界限(有界或无界)。无界模式下可以不断添加元素,直到耗尽系统资源。有界模式则类似于
ArrayBlockingQueue,但吞吐量通常较高。 - PriorityBlockingQueue:一个无界的优先级队列,元素按照自然顺序或者指定的比较器顺序进行排序。与其他阻塞队列不同的是,PriorityBlockingQueue 不保证元素的 FIFO 顺序。
- DelayQueue:一个无界队列,队列中的元素必须实现
Delayed接口,只有当元素的延迟时间到期时,才能被取出。常用于延迟任务调度。 - SynchronousQueue:一个没有内部容量的队列,每个插入操作必须等待对应的移除操作,反之亦然。常用于在线程之间的直接传递任务,而不是存储任务。
扩展知识
使用场景
- ArrayBlockingQueue 和 LinkedBlockingQueue 常用于典型的生产者-消费者场景。例如:任务处理系统中,生产者生成任务,消费者从队列中取出任务并执行。
- PriorityBlockingQueue 更适合处理带有优先级的任务场景,如任务调度系统。
- DelayQueue 适用于需要延迟处理的任务,例如:缓存失效策略、定时任务调度等。
- SynchronousQueue 适合在线程间直接传递数据,而不希望数据被存储在队列中。例如:
ThreadPoolExecutor的直接交接模式中使用 SynchronousQueue 来传递任务。
ArrayBlockingQueue 和 LinkedBlockingQueue 区别
常见的有 ArrayBlockingQueue 和 LinkedBlockingQueue,分别是基于数组和链表的有界阻塞队列。
两者原理都是基于 ReentrantLock 和 Condition 。
ArrayBlockingQueue 基于数组,内部实现只用了一把锁,可以指定公平或者非公平锁。
LinkedBlockingQueue 基于链表,内部实现用了两把锁,take 一把、put 一把,所以入队和出队这两个操作是可以并行的,从这里看并发度应该比 ArrayBlockingQueue 高。
LinkedTransferQueue
LinkedTransferQueue,相对于其他阻塞队列从名字来看它有 Transfer 功能,其实也不是什么神奇功能,一般阻塞队列都是将元素入队,然后消费者从队列中获取元素。
而 LinkedTransferQueue 的 transfer 是元素入队的时候看看是否已经有消费者在等了,如果有在等了直接给消费者即可,所以就是这里少了一层,没有锁操作。



















