一、互斥与同步
- 临界资源互斥访问:任一时刻只有一个线程可以访问阻塞队列,对其进行添加/删除元素的操作
- 线程的同步:若队列已满,生产者线程挂起;若队列已空;消费者线程挂起;生产者生产元素后唤醒消费者、消费者消费元素后唤醒生产者
二、实现分析
- 对阻塞队列加锁的实现互斥访问
- wait/notifyAll实现线程的同步
三、代码实现
@Slf4j
public class Producer implements Runnable{
// 阻塞队列
Queue<Integer> queue;
//队列大小
private int MAX_SIZE;
// 随机生产元素
Random random = new Random();
public Producer(Queue queue,int MAX_SIZE){
this.queue = queue;
this.MAX_SIZE = MAX_SIZE;
}
@Override
public void run() {
synchronized (queue){
// while避免虚假唤醒
while (queue.size() == MAX_SIZE){
try {
// 需要挂起当前线程,释放queue上的锁
// 让消费者线程可以获取该锁,并获取队列中的元素
queue.wait();
log.debug("队列已满,需要挂起....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 队列未满则生成元素,
int i = random.nextInt();
queue.offer(i);
log.debug("队列未满,生产者生产元素{}",i);
log.debug("当前队列大小为{}",queue.size());
// 唤醒消费者线程
queue.notifyAll();
log.debug("唤醒消费者/生产者线程");
}
}
}
@Slf4j
public class Consumer implements Runnable{
// 阻塞队列
Queue<Integer> queue;
public Consumer(Queue queue){
this.queue = queue;
}
@Override
public void run() {
synchronized (queue){
// while避免虚假唤醒
while (queue.size() == 0){
try {
// 需要挂起当前线程,释放queue上的锁
// 让生产者线程可以获取该锁,并生产元素
queue.wait();
log.debug("队列已空,消费者线程需要挂起....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 队列不为0则获取元素
int i = queue.poll();
log.debug("消费者线程消费了元素{}",i);
log.debug("当前队列大小为{}",queue.size());
// 唤醒消费者线程
queue.notifyAll();
log.debug("唤醒消费者/生产者线程");
}
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Random random = new Random();
Queue<Integer> queue = new LinkedList<>();
int MAX_SIZE=10;
for(int i=0;i<10;i++){
queue.offer(random.nextInt(500));
}
for (int i=0;i<5;i++){
new Thread(new Producer(queue,MAX_SIZE){
}).start();
new Thread(new Consumer(queue){
}).start();
}
}
}
四、运行结果
五、缺陷和改进
- 可以看到在多个生产者和消费者的情况下,notifyAll会唤醒两种线程。也就是说,有可能消费者线程运行之后,运行的还是消费者线程。
后续可以采用condition进行改进。