【多线程】如何实现一个阻塞队列

如何实现一个阻塞队列

一.什么是阻塞队列

  • 阻塞队列的作用是帮助我们更加方便地进行线程通信和协作1。阻塞队列区别于其他类型的队列的最主要的特点就是“阻塞”这两个字2。阻塞功能使得生产者和消费者两端的能力得以平衡,当有任何一端速度过快时,阻塞队列便会把过快的速度给降下来2。阻塞队列有三种用法:返回特殊值、一直阻塞和超时退出3。阻塞队列可以很好地缓冲线程池创建线程需要获取mainlock这个全局锁的影响,并且可以避免新任务的到达速率超过了线程池的处理速率4。
  • 特点:
    ①阻塞队列是线程安全的
    ②.具有阻塞特性
    • 如果针对一个已经满了的阻塞队列做入队操作,就会产生阻塞.一直阻塞到队列不满(出队列)之后.
    • 如果针对一个空了的阻塞队列做出队操作,就会产生阻塞,一直阻塞到队列不空(入队列)之后.

二.如何实现一个阻塞队列

基本步骤:
①实现一个普通队列:②保证线程安全:③.加上阻塞功能.

①实现一个普通队列

代码实现:

class MyBlockingQueue{
    private String[] elems;
    private int head = 0;
    //表示队列的对头位置
    private int tail = 0;
    //表示队列的队尾位置
    private int usedSize = 0;
    //用于需要判断队列空或队列满的情况,表示队列中有几个元素.
    public MyBlockingQueue(int capacity){
        elems = new String[capacity];
    }
    //构造方法,并传参说明容量.
    public void put(String elemt){
        if(usedSize==elems.length){
            //判断队列是否满了,如果满了应该阻塞等待
        }
        elems[tail] = elemt;
        tail++;
        if(tail>=elems.length){
            tail = 0;
        }
        //如果tail到最后之后,需要把他重新放到0位置.
        usedSize++;
    }
    //实现入队操作
    public String take(){
        if(usedSize == 0){
            //判断是否为空,如果为空应该阻塞等待.
        }
        String ret = elems[head];
        head++;
        if(head>=elems.length){
            head = 0;
        }
        //如果head到最后之后,需要把他重新放到0位置.
        usedSize--;
        return ret;
    }

}

②.保证线程安全

代码实例:

class MyBlockingQueue{
    private String[] elems;
    private int head = 0;
    //表示队列的对头位置
    private int tail = 0;
    //表示队列的队尾位置
    private int usedSize = 0;
    //用于需要判断队列空或队列满的情况,表示队列中有几个元素.
    public MyBlockingQueue(int capacity){
        elems = new String[capacity];
    }
    //构造方法,并传参说明容量.
    synchronized public void put(String elemt){
        if(usedSize==elems.length){
            //判断队列是否满了,如果满了应该阻塞等待
        }
        elems[tail] = elemt;
        tail++;
        if(tail>=elems.length){
            tail = 0;
        }
        //如果tail到最后之后,需要把他重新放到0位置.
        usedSize++;
    }
    //实现入队操作
    synchronized public String take(){
        if(usedSize == 0){
            //判断是否为空,如果为空应该阻塞等待.
        }
        String ret = elems[head];
        head++;
        if(head>=elems.length){
            head = 0;
        }
        //如果head到最后之后,需要把他重新放到0位置.
        usedSize--;
        return ret;
    }

}

此处的synchronized给方法加锁,等于给this加锁.也可以用synchronized给具体的代码加锁.

③.加上阻塞功能

代码实例:

class MyBlockingQueue{
    private String[] elems;
    private int head = 0;
    //表示队列的对头位置
    private int tail = 0;
    //表示队列的队尾位置
    private int usedSize = 0;
    //用于需要判断队列空或队列满的情况,表示队列中有几个元素.
    public MyBlockingQueue(int capacity){
        elems = new String[capacity];
    }
    //构造方法,并传参说明容量.
    synchronized public void put(String elemt) throws InterruptedException {
        if(usedSize==elems.length){
            //判断队列是否满了,如果满了应该阻塞等待
            this.wait();
        }
        elems[tail] = elemt;
        tail++;
        if(tail>=elems.length){
            tail = 0;
        }
        //如果tail到最后之后,需要把他重新放到0位置.
        usedSize++;
        this.notify();
        //入队列之后,要唤醒由于队列空出队列时出现的阻塞等待.
    }
    //实现入队操作
    synchronized public String take() throws InterruptedException {
        if(usedSize == 0){
            //判断是否为空,如果为空应该阻塞等待.
            this.wait();
        }
        String ret = elems[head];
        head++;
        if(head>=elems.length){
            head = 0;
        }
        //如果head到最后之后,需要把他重新放到0位置.
        usedSize--;
        this.notify();
        //出队列之后,要唤醒由于队列满入队列时出现的阻塞等待.
        return ret;
    }

}

此时会出现一个问题:如果有两个线程一号线程和二号线程,同时入队列但队列为空,又有一个线程线程三,线程三入队列,入队列之后,队列不为空了,此时会随机唤醒一个因为线程空而正在阻塞的线程(即线程一或者线程二),如果唤醒的时线程一,那么线程一继续向下执行,成功入队列,此时线程一会唤醒线程二,但是,此时队列为空,代码就会出现线程安全问题

解决办法:将判断队列空满变为while循环.

代码实例:

class MyBlockingQueue{
    private String[] elems;
    private int head = 0;
    //表示队列的对头位置
    private int tail = 0;
    //表示队列的队尾位置
    private int usedSize = 0;
    //用于需要判断队列空或队列满的情况,表示队列中有几个元素.
    public MyBlockingQueue(int capacity){
        elems = new String[capacity];
    }
    //构造方法,并传参说明容量.
    synchronized public void put(String elemt) throws InterruptedException {
       
        //入队列之后,要唤醒由于队列空出队列时出现的阻塞等待.
        //判断队列是否满了,此处换成while,就意味着线程被唤醒之后,要再判定一次条件
        //wait之前判定了一次,wait之后又判定了一次(相当于做了进一步判断)
        while(usedSize==elems.length){
            //判断队列是否满了,如果满了应该阻塞等待
            this.wait();
        }
        elems[tail] = elemt;
        tail++;
        if(tail>=elems.length){
            tail = 0;
        }
        //如果tail到最后之后,需要把他重新放到0位置.
        usedSize++;
        this.notify();
    }
    //实现入队操作
    synchronized public String take() throws InterruptedException {
        //判断队列是否满了,此处换成while,就意味着线程被唤醒之后,要再判定一次条件
        //wait之前判定了一次,wait之后又判定了一次(相当于做了进一步判断)
        while(usedSize == 0){
            //判断是否为空,如果为空应该阻塞等待.
            this.wait();
        }
        String ret = elems[head];
        head++;
        if(head>=elems.length){
            head = 0;
        }
        //如果head到最后之后,需要把他重新放到0位置.
        usedSize--;
        this.notify();
        //出队列之后,要唤醒由于队列满入队列时出现的阻塞等待.
        return ret;
    }

} 

判断队列是否满了,此处换成while,就意味着线程被唤醒之后,要再判定一次条件
wait之前判定了一次,wait之后又判定了一次(相当于做了进一步判断)

总结

阻塞队列是基于普通队列实现的,与多线程联系到一起。主要是对wait()与notify()操作的应用.因此在Java标准库中,wait往往是和while在一起用的

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值