生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个缓冲区(Buffer),生产者往 Buffer 中添加产品,消费者从 Buffer 中取走产品,当 Buffer 为空时,消费者阻塞,当 Buffer 满时,生产者阻塞。
Kotlin 中有多种方法可以实现多线程的生产/消费模型(大多也适用于Java)
- Synchronized
- ReentrantLock
- BlockingQueue
- Semaphore
- PipedXXXStream
- RxJava
- Coroutine
- Flow
1. Synchronized
ynchronized 是最最基本的线程同步工具,配合wait
/notify
可以实现实现生产消费问题
val buffer = LinkedList<Data>()
val MAX = 5 //buffer最大size
val lock = Object()
fun produce(data: Data) {
sleep(2000) // mock produce
synchronized(lock) {
while (buffer.size >= MAX) {
// 当buffer满时,停止生产
// 注意此处使用while不能使用if,因为有可能是被另一个生产线程而非消费线程唤醒,所以要再次检查buffer状态
// 如果生产消费两把锁,则不必担心此问题
lock.wait()
}
buffer.push(data)
// notify方法只唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
// notifyAll会唤醒所有等待中线程,哪一个线程将会第一个处理取决于操作系统的实现,但是都有机会处理。
// 此处使用notify有可能唤醒的是另一个生产线程从而造成死锁,所以必须使用notifyAll
lock.notifyAll()
}
}
fun consume() {
synchronized(lock) {
while (buffer.isEmpty())
lock.wait() // 暂停消费
buffer.removeFirst()
lock.notifyAll()
}
sleep(2000) // mock consume
}
@Test
fun test() {
// 同时启动多个生产、消费线程
repeat(10) {
Thread { produce(Data()) }.start()
}
repeat(10) {
Thread { consume() }.start()
}
}
2. ReentrantLock
Lock 相对于 Synchronized 好处是当有多个生产线/消费线程时,我们可以通过定义多个condition
精确指定唤醒哪一个。下面的例子展示 Lock 配合await
/single
替换前面 Synchronized 写法
val buffer = LinkedList<Data>()
val MAX = 5 //buffer最大size
val lock = ReentrantLock()
val condition = lock.newCondition()
fun produce(data: Data) {
sleep(2000) // mock produce
lock.lock()
while (buffer.size >= 5)