java多线程面试之手写阻塞队列,实现生产者和消费者模式。

什么是阻塞队列

首先,阻塞队列是一个队列,满足队列的基本数据结构,先进先出。其次,当队列满时,队列会阻塞插入元素的线程,直到队列不满;当队列空时,获取元素的线程会等待队列变为非空。

阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。

如何写一个阻塞队列

手写阻塞队列是多线程面试中常见的问题,能考察面试者对多线程和锁的基础知识。

通过synchronized关键字配合wait()notify()方法,实现线程的交替运行:

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.CyclicBarrier;

public class MyBlockingQueue {

    //队列
    private  final Queue<String> myQueue = new LinkedList<>();

    //最大长度
    private static final int MAXSIZE = 20;
    private static final int MINSIZE = 0;

    //获取队列长度
    public int getSize(){
        return myQueue.size();
    }

    //生产者
    public void push(String str) throws Exception {
        //拿到对象锁
        synchronized (myQueue){

            //如果队列满了,则阻塞
            while(getSize() == MAXSIZE){
                myQueue.wait();
            }

            myQueue.offer(str);
            System.out.println(Thread.currentThread().getName() + "放入元素" + str);
            //唤醒消费者线程,消费者和生产者自己去竞争锁
            myQueue.notify();
        }
    }

    //消费者
    public String pop() throws Exception {
        synchronized (myQueue){
            String result = null;

            //队列为空则阻塞
            while(getSize() == MINSIZE){
                myQueue.wait();
            }
            //先进先出
            result = myQueue.poll();

            System.out.println(Thread.currentThread().getName()+"取出了元素" + result);
            //唤醒生产者线程,消费者和生产者自己去竞争锁
            myQueue.notify();

            return result;
        }
    }

    public static void main(String args[]){

        MyBlockingQueue myBlockingQueue = new MyBlockingQueue();

        //两个线程,都执行完成了打印
        CyclicBarrier barrier = new CyclicBarrier(2, ()->{
            System.out.println("生产结束,下班了,消费者明天再来吧!");
        });

		//生产者线程
        new Thread(()->{
            //50个辛勤的生产者循环向队列中添加元素
            try {
                for(int i = 0; i < 50; i++){
                    myBlockingQueue.push("——" + i );
                }
                //生产完了
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"生产者").start();

		//消费者线程
        new Thread(()->{
            //50个白拿的消费者疯狂向队列中获取元素
            try {
                for(int j = 0; j < 50; j++){
                    myBlockingQueue.pop();
                }
                //消费完了
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"消费者").start();

    }
}

运行结果(部分)

在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/1a8e33ab0a4743d787a60dd596e9f5a0.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBATWFZdUthbmc=,size_15,color_FFFFFF,t_70,g_se,x_16

对代码的一些解释

1、private final Queue<String> myQueue = new LinkedList<>();这样创建的队列实际上偷懒了,并不是完全用数组实现的队列,用了封装的队列,但毕竟重点不在于此。
2、使用了JUC包的CyclicBarrier类,通过调用await()方法,实现两个线程都执行完毕后,再执行相关代码,这不属于阻塞队列的范畴。
3、synchronized块中使用的对象锁不一定要用myQueue,使用同一个对象就行。
4、new Thread(()->{...},"生产者").start();是java8函数式写法,等同于继承Runnable覆写run方法。

对运行结果的一些解释

1、当队列被填满或者为空时,当前线程才会被挂起等待另一个线程唤醒锁
2、并不是队列被生产者填满了,消费者才会去取,而是在队列没满的时候,生产者和消费者两个线程都会去竞争资源,谁先拿到锁,谁先执行。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值