[JAVAee]阻塞队列

目录

阻塞队列的含义

阻塞队列的使用 

阻塞队列的模拟实现

①实现循环队列,与普通的put与take方法 

②添加阻塞功能 

③保证线程安全 

注意事项:


阻塞队列的含义

有队列这两个字的,少不了"先进先出"这个特性

阻塞队列是一种线程安全的数据结构,主要的特性有:

  • 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
  • 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.

阻塞队列的使用 

在java标准库中,内置了阻塞队列.当我们想使用的时候,可以实现BlockingQueue接口就好(但其真正实现的类为LinkedBlockingQueue).

在队列当中,必然有方法将元素进行放置或取出.

在阻塞队列中,常用的为put方法放置元素与take方法取出元素,也只有这两种方法具有阻塞的特性.能实现阻塞队列的效果.

public static void main(String[] args) throws InterruptedException {
         BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
         queue.put(5);//放置一个元素,如果当前队列已经满了则此线程进入阻塞状态
         queue.take();//取出一个元素,如果当前队列为空则此线程进入阻塞状态
    }

阻塞队列的模拟实现

实现阻塞队列:

  1. 实现一个循环队列()
  2. 给放置与取出方法加上阻塞功能
  3. 保证线程安全

①实现循环队列,与普通的put与take方法 

class MyBlockingQueue{
    private int[] array = new int[1000];//基于数组来实现队列
    private int size = 0;//记录队列当前的元素个数
    private int head = 0;//记录头节点
    private int tail = 0;//记录尾巴节点

    public void put(int value){
        if(size == array.length){
            //检测队列中的元素是否已满
            System.out.println("已满,勿扰");
            return;
        }
        array[tail++] = value;//尾插法
        size++;
        if(tail == array.length){
            tail = 0;//达到循环队列的效果
        }
    }

    public int take(){
        if(size == 0){
            System.out.println("空了空了");
            return -1;
        }
        int ret = array[head];
        head++;
        if(head == array.length){
            head = 0;//循环循环
        }
        size--;
        return ret;
    }
}

②添加阻塞功能 

分别往put与take方法加上wait与notifyAll

  • put时队列满的时候进入阻塞状态,只有等到调用take方法取走一个元素后才会唤醒线程
  • take时队列空的时候进入阻塞状态,只有等到调用put方法放置一个元素后才会唤醒线程
public void put(int value) throws InterruptedException {
        if(size == array.length){
            //检测队列中的元素是否已满
            wait();
        }
        array[tail++] = value;//尾插法
        size++;
        if(tail == array.length){
            tail = 0;//达到循环队列的效果
        }
        notifyAll();
    }
public int take()throws InterruptedException{
        if(size == 0){
            //检测队列是否为空
            wait();
        }
        int ret = array[head];
        head++;
        if(head == array.length){
            head = 0;//循环循环
        }
        size--;
        notifyAll();
        return ret;
    }

③保证线程安全 

关于线程安全要注意的是:

  • 多线程环境下的抢占式执行
  • 多个线程修改同一个变量
  • 线程的读写操作不能保证原子性

 对于此处的读写修改操作,emmm.

put方法和take方法好像一整个都是呢

所以我们可以使用synchronized关键字为他们上锁,保证原子性达到线程安全.

对于频繁修改和需要进行比较或判断的元素,也要加上volatile关键字,保证线程内存可见性.防止存在误判导致的一系列错误.

class MyBlockingQueue{
    private int[] array = new int[1000];//基于数组来实现队列
    private volatile int size = 0;//记录队列当前的元素个数
    private int head = 0;//记录头节点
    private int tail = 0;//记录尾巴节点

    public void put(int value) throws InterruptedException {
        synchronized (this) {
            while(size == array.length){//使用while循环,线程被唤醒后继续判断队列是否已满
                //真正使用的情况下不但只有两个线程
                //检测队列中的元素是否已满
                wait();
            }
            array[tail++] = value;//尾插法
            size++;
            if(tail == array.length){
                tail = 0;//达到循环队列的效果
            }
            notifyAll();
        }
    }

    public int take()throws InterruptedException{
        synchronized (this) {
            while(size == 0){
                //检测队列是否为空
                wait();
            }
            int ret = array[head];
            head++;
            if(head == array.length){
                head = 0;//循环循环
            }
            size--;
            notifyAll();
            return ret;
        }
    }
}

注意事项:

使用循环队列,因为线程是先进先出的情况.当尾巴节点来到了最后一个下标的时候,如果前面有进行take操作其实队列前半段是空空如也的.可以继续存放数据

在多线程的环境下,一定要注意线程安全.注意修改操作

在多线程下,判断队列是否已满或是否为空的语句最好写成while循环,在多线程下.可能线程A被唤醒后没有立即被调度,过一段时间后队列又满了,但此时如果是if语句,就无法判断队列是否满了.再进行操作会有其他数据被覆盖的风险

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值