实现Java 阻塞队列

在自己实现之前先搞清楚阻塞队列的几个特点:
基本队列特性:先进先出。
写入队列空间不可用时会阻塞。
获取队列数据时当队列为空时将阻塞。

实现队列的方式多种,总的来说就是数组和链表;其实我们只需要搞清楚其中一个即可,不同的特性主要表现为数组和链表的区别。

这里的 ArrayBlockingQueue 看名字很明显是由数组实现。

我们先根据它这三个特性尝试自己实现试试。

初始化队列

我这里自定义了一个类:ArrayBlockQueue,它的构造函数如下:

//队列参数
    public int size;

    private volatile Object[] items;

    private volatile int pollPoint = 0;

    private volatile int addPoint = 0;

    private volatile int count = 0;

//初始化队列容量
    public ArrayBlockQueue(int size) {
        this.size = size;
        this.items = new Object[size];
    }

写入操作

有几个需要注意的点:

  • 队列满的时候,写入的线程需要被阻塞。
  • 写入过队列的数量大于队列大小时需要从第一个下标开始写。
  • 先看第一个队列满的时候,写入的线程需要被阻塞,先来考虑下如何才能使一个线程被阻塞,看起来的表象线程卡住啥事也做不了。
其实这样的一个特点很容易让我们想到 Java 的等待通知机制来实现线程间通信。
所以我这里的做法是,一旦队列满时就将写入线程调用 addLock.wait() 进入 waiting 状态,直到空间可用时再进行唤醒。

初始化 两个锁

//初始化两个锁
    private Object addLock = new Object();

    private Object pollLock = new Object();

写入操作代码

public void add(Object item) {

        synchronized (addLock) {
            //队列满则阻塞
            while (count >= size) {
                try {
                    addLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            items[addPoint] = item;
            addPoint = (addPoint + 1) % size;
            count++;

            //当队列从0个元素添加到1个元素,则说明从空状态转非空状态可以通知一次取元素线程
            if (count == 1)
                synchronized (pollLock) {
                    pollLock.notifyAll();
                }
        }
    }

所以这里声明了两个对象用于队列满、空情况下的互相通知作用。

在写入数据成功后需要使用 pollLock.notifyAll(),这样的目的是当获取队列为空时,一旦写入数据成功就可以把消费队列的线程唤醒。

这里的 wait 和 notify 操作都需要对各自的对象使用 synchronized 方法块,这是因为 wait 和 notifyAll 都需要获取到各自的锁。

消费队列

上文也提到了:当队列为空时,获取队列的线程需要被阻塞,直到队列中有数据时才被唤醒。
代码和写入的非常类似,也很好理解;只是这里的等待、唤醒恰好是相反的。
总的来说就是:

  • 写入队列满时会阻塞直到获取线程消费了队列数据后唤醒写入线程。
  • 消费队列空时会阻塞直到写入线程写入了队列数据后唤醒消费线程。
public Object poll() {

        synchronized (pollLock) {
            //队列空则阻塞
            while (count == 0) {
                try {
                    pollLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Object value = items[pollPoint];
            pollPoint = (pollPoint + 1) % size;
            count--;

            //当队列从满到非满,可以通知一次增添元素的线程
            if (count == (size - 1))
                synchronized (addLock) {
                    addLock.notifyAll();
                }
            return value;
        }
    }

测试

每一秒出队1个

//消费者
//每一秒出队1个
class Counsum extends Thread {

    private ArrayBlockQueue queue;

    public Counsum(ArrayBlockQueue queue) {
        this.queue = queue;
    }

    @Override
    public void run() {

        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 出队一个数据:   " + queue.poll());
        }
    }
}

每5秒入队2个,可见生产明显慢于消费

//生产者
//每5秒入队2个
class Product extends Thread {

    private ArrayBlockQueue queue;

    public Product(ArrayBlockQueue queue) {
        this.queue = queue;
    }

    @Override
    public void run() {

        int i = 0;
        while (true) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            for (int j = 0; j < 2; j++, i++) {

                queue.add(i);
                System.out.println(Thread.currentThread().getName() + "【入队一个数据:   " + i+"】");
            }
        }
    }
}

main

public class Test extends Object {

    public static void main(String[] args) throws CloneNotSupportedException {
        
        ArrayBlockQueue queue = new ArrayBlockQueue(5);
        
        Thread counsum = new Counsum(queue);
        Thread counsum1 = new Counsum(queue);
        Thread counsum2 = new Counsum(queue);

        Thread product = new Product(queue);
        Thread product1 = new Product(queue);
        Thread product2 = new Product(queue);
        counsum.start();
        product.start();
        counsum1.start();
        product1.start();
        counsum2.start();
        product2.start();
    }
}
//output
消费者线程-1出队一个数据:   0
生成者线程-3入队一个数据:    0
消费者线程-3出队一个数据:   0
消费者线程-2出队一个数据:   0
生成者线程-2入队一个数据:    0
生成者线程-1入队一个数据:    0
生成者线程-2入队一个数据:    1
生成者线程-3入队一个数据:    1
生成者线程-1入队一个数据:    1
消费者线程-3出队一个数据:   1
消费者线程-1出队一个数据:   1
消费者线程-2出队一个数据:   1
消费者线程-2出队一个数据:   2
生成者线程-2入队一个数据:    2
消费者线程-1出队一个数据:   2
生成者线程-3入队一个数据:    2
生成者线程-1入队一个数据:    2
消费者线程-3出队一个数据:   2
生成者线程-1入队一个数据:    3
生成者线程-3入队一个数据:    3
生成者线程-2入队一个数据:    3
消费者线程-1出队一个数据:   3
消费者线程-3出队一个数据:   3
消费者线程-2出队一个数据:   3

使用lock锁版本

package concurrent;

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

public class ArrayBlockQueue {

    //队列参数
    public int size;

    private volatile Object[] items;

    private volatile int pollPoint = 0;

    private volatile int addPoint = 0;

    private volatile int count = 0;

    //初始化两个锁
    private ReentrantLock addLock = new ReentrantLock();
    private ReentrantLock pollLock = new ReentrantLock();

    //初始化两个信号量
    private Condition full =addLock.newCondition();
    private Condition empty =pollLock.newCondition();


    //初始化队列容量
    public ArrayBlockQueue(int size) {
        this.size = size;
        this.items = new Object[size];
    }


    public void add(Object item) {

        addLock.lock();
        {
            //队列满则阻塞
            while (count >= size) {
                try {
                    full.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            items[addPoint] = item;
            addPoint = (addPoint + 1) % size;
            count++;

            //当队列从0个元素添加到1个元素,则说明从空状态转非空状态可以通知一次取元素线程
            if (count == 1) {
                pollLock.lock();
                empty.signal();
                pollLock.unlock();
            }
        }
        addLock.unlock();
    }

    public Object poll() {

        Object value;
        pollLock.lock();
        {
            //队列空则阻塞
            while (count == 0) {
                try {
                    empty.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            value = items[pollPoint];
            pollPoint = (pollPoint + 1) % size;
            count--;

            //当队列从满到非满,可以通知一次增添元素的线程
            if (count == (size - 1)) {
                addLock.lock();
                full.signal();
                addLock.unlock();
            }
        }
        pollLock.unlock();
        return value;
    }

}

引用


赵栩彬
358 声望600 粉丝

愿,你在遭受打击时,记起你的珍贵,抵抗恶意;