MST

星途 面试题库

面试题:Java多线程条件变量的复杂场景应用

假设有一个多线程场景,有多个生产者线程向一个共享队列添加数据,同时有多个消费者线程从该队列中取出数据。当队列满时,生产者线程等待;当队列空时,消费者线程等待。请使用Java的条件变量(Condition)实现这个场景,并详细说明实现思路及代码中如何处理线程安全问题。
49.3万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

实现思路

  1. 共享队列:使用LinkedList来实现共享队列,它是线程不安全的,所以需要在操作时保证线程安全。
  2. 线程安全控制:使用ReentrantLock来保证对共享队列操作的线程安全,通过Condition实现生产者和消费者线程的等待与唤醒。
  3. 生产者线程:当队列满时,调用Conditionawait方法等待,队列不满时向队列添加数据,并调用ConditionsignalAll方法唤醒所有等待的消费者线程。
  4. 消费者线程:当队列空时,调用Conditionawait方法等待,队列不空时从队列取出数据,并调用ConditionsignalAll方法唤醒所有等待的生产者线程。

代码实现

import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ProducerConsumerExample {
    private final LinkedList<Integer> queue;
    private final int capacity;
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    public ProducerConsumerExample(int capacity) {
        this.queue = new LinkedList<>();
        this.capacity = capacity;
    }

    public void produce(int item) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == capacity) {
                System.out.println("Queue is full. Producer waiting...");
                notFull.await();
            }
            queue.add(item);
            System.out.println("Produced: " + item);
            notEmpty.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public int consume() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                System.out.println("Queue is empty. Consumer waiting...");
                notEmpty.await();
            }
            int item = queue.removeFirst();
            System.out.println("Consumed: " + item);
            notFull.signalAll();
            return item;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ProducerConsumerExample pc = new ProducerConsumerExample(5);

        Thread producer1 = new Thread(() -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    pc.produce(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread producer2 = new Thread(() -> {
            try {
                for (int i = 11; i <= 20; i++) {
                    pc.produce(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread consumer1 = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    pc.consume();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread consumer2 = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    pc.consume();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        producer1.start();
        producer2.start();
        consumer1.start();
        consumer2.start();

        try {
            producer1.join();
            producer2.join();
            consumer1.join();
            consumer2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程安全处理

  1. ReentrantLock:在produceconsume方法中,通过lock.lock()获取锁,确保在同一时间只有一个线程可以访问共享队列,避免数据竞争。
  2. Condition:使用notFullnotEmpty两个Condition对象。当队列满时,生产者线程调用notFull.await()等待,当队列空时,消费者线程调用notEmpty.await()等待。当生产者向队列添加数据后,调用notEmpty.signalAll()唤醒所有等待的消费者线程;当消费者从队列取出数据后,调用notFull.signalAll()唤醒所有等待的生产者线程。
  3. try - finally:在获取锁后,使用try - finally块确保无论是否发生异常,都能正确释放锁,通过lock.unlock()来释放锁,保证线程安全。