阻塞队列 BlockingQueue 的使用之 ArrayBlockingQueue (二)

一、概述:

     ArrayBlockingQueue是基于数组的、定长(容量固定)的、线程安全的阻塞队列。

二、常用方法:

    1、往队列中添加元素的方法有:add(E e)、put(E e)、offer(E e)、offer(E e, long timeout, TimeUnit unit)

    2、移除队列中元素的方法有:remove(E e)、poll()、poll(long timeout, TimeUnit unit)、take()

    1)、add(E e) 方法:ArrayBlockingQueue的 add() 方法 当队列满后,再添加元素就会抛出异常

    2)、put(E e)方法:当队列满了之后, 会阻塞在 put() 方法那里 一直等待队列存在可用空间时才会继续执行往队列中添加元 素入股在等待的过程中被中断,则会抛出 InterruptedException

    3)、offer(E e)方法:返回一个boolean类型的值,如果队列已满,则添加失败返回false,否则就添加成功返回true。不会因为队列已满无法添加进目标元素进队列里而抛出异常 true:代表添加元素成功。该方法不会阻塞。

    4)、offer(E e, long timeout, TimeUnit unit)方法:返回一个boolean类型的值,如果队列已满,则添加失败返回false,否 则就添加成功返回true。不会因为队列已满无法添加进目标元素进队列里而抛出异常。该方法的后面两个参数分别是时间长度和时间单位,如果队列已满,该方法会等待指定的时间,如果在等待了指定的时间后队列依然没有空间,则抛弃该元素不让其入队(这里可以看作是设置阻塞指定时间,不成功则抛弃)。会抛出 InterruptedException

    5)、remove() 方法: 该方法会返回一个boolean类型的值表示是否移除成功, 如果需要移除的元素在队列中不存在的话,不会 抛出异常。

    6)、poll() 方法:获取并且移除队列的头元素,如果队列为空的话则返回null,不会抛出异常

    7)、poll(long timeout, TimeUnit unit) 方法:获取并移除队列的头元素,可以控制阻塞时间,在队列中没有元素时会一直 等待(阻塞),直到队列中有新的可用元素,如果等待到指定时间队列中依然没有可用元素则返回 null。如果在等待时被中断会抛出InterruptedException

    8)、take() 方法:该方法会阻塞,如果队列里没有元素时就会阻塞,知道对立中有元素时则取队列的头元素。如果在等待时中 断会抛出 InterruptedException

三、常用方法测试例子与简单分析:

    1)、add(E e) 测试方法:

/**
     * ArrayBlockingQueue的 add() 方法 当队列满后,再添加元素就会抛出异常
     * @param queue 长度为 2 的 ArrayBlockingQueue
     */
    private static void testAdd(BlockingQueue<Integer> queue) {
        // 超过容量后会直接抛出异常,所以在使用 ArrayBlockingQueue 时需要注意和处理队列的长度问题
        for (int i = 0; i < 10; i++) {
            if (i == 2) {
                System.out.println("第3个");
            }
            queue.add(i);
        }
        // 抛出空指针异常,队列不接受 null 元素
        Integer integer = null;
        queue.add(integer);
    }

add(E e)方法实际上最终调用的是offer(E e) 在JDK源码里面做了元素的为空校验,以下为JDK1.8中的部分源码:

public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length)
                return false;
            else {
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }

从上面的代码中就可以看出,对参数 e 做了为空校验,使用重入锁保证线程安全,并且没有阻塞,如果队列已满,直接返回false,并且在add的实现中抛出 IllegalStateException("Queue full");

    2)、put(E e)方法测试方法:

/**
     * ArrayBlockingQueue的 put() 方法 当队列满了之后, 
     * 会阻塞在 put() 方法那里 
     * 一直等待队列存在可用空间时才会继续执行往队列中添加元素
     *
     * @param queue BlockingQueue队列
     */
    private static void testPut(BlockingQueue<Integer> queue) {
        int i = 0;
        Thread thread = new Thread("ArrayBlockingQueue队列 task() 方法测试线程") {
            @Override
            public void run() {
                do {
                    try {
                        Thread.sleep(2000L);
                        System.out.println("获取队列中的元素" + queue.take());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } while (!queue.isEmpty());
            }
        };
        thread.start();
        for (i = 0; i < 10; i++) {
            System.out.println("第" + i + "个");
            try {
                queue.put(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

如上代码中所示,该方法会抛出InterruptedException异常。当队列满了之后,这里利用重入锁使线程达到条件时等待,在构造ArrayBlockingQueue时就会构造两个条件一个是当队列满时,一个是队列中不存在元素时(notEmpty、notFull)。以下为JDK1.8中的部分代码:

public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }
public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }
private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();
    }

从以上代码中可以看出如果当条件满足时(队列中的元素个数与构建时指定的队列元素个数相等),则让线程等待,通过这种方式以达到阻塞队列的作用。同时也优先响应中断,而不是获取或冲入获取的。

    3)、offer(E e)方法测试:

/**
     * 返回一个boolean类型的值,如果队列已满,则添加失败返回false,否则就添加成功返回true。 
     * 不会因为队列已满无法将目标元素添加进队列里而抛出异常 true:代表添加元素成功
     * false:代表添加元素失败
     */
    private static void testOffer1(BlockingQueue<Integer> queue) {
        for (int i = 0; i < 10; i++) {
            boolean flag = queue.offer(i);
            System.out.println("第" + i + "个" + flag);
        }
    }

以下是源码:

public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length)
                return false;
            else {
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }
/**
     * Inserts element at current put position, advances, and signals.
     * Call only when holding lock.
     */
    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();
    }

如需要了解原理的话,可以自行查看源码,下面的就不再分析和贴出源码了。

4)、offer(E elong timeoutTimeUnit unit)方法:

/**
     * 返回一个boolean类型的值,如果队列已满,则添加失败返回false,否则就添加成功返回true。
     * 不会因为队列已满无法添加进目标元素进队列里而抛出异常
     * 该方法的后面两个参数分别是时间长度和时间单位,如果队列已满,
     * 如下代码所示:该方法会等待5秒,如果5秒后队列依然没有空间,则抛弃该元素不让其入队(这里可以看作是设置阻塞5秒,不成功则抛弃)
     * true:代表添加元素成功 false:代表添加元素失败
     */
    private static void testOffer2(BlockingQueue<Integer> queue) {
        try {
            for (int i = 0; i < 10; i++) {
                boolean flag = queue.offer(i, 5, TimeUnit.SECONDS);
                System.out.println("第" + i + "个" + flag);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
5)、 remove() 方法:
/**
     * ArrayBlockingQueue的 remove() 方法,
     * 该方法会返回一个boolean类型的值表示是否移除成功,
     * 如果需要移除的元素在队列中不存在的话,不会抛出异常。
     */
    private static void testRemove(BlockingQueue<Integer> queue) {
        for (int i = 0; i < 5; i++) {
            System.out.println(i);
            boolean flag = queue.remove(i);
            System.out.println(queue.isEmpty() +"------"+ flag);
        }
    }
6)、 poll() 方法:
/**
     * ArrayBlockingQueue的 poll() 方法, 获取并且移除队列的头元素,如果队列为空的话则返回null,不会抛出异常
     */
    private static void testPoll(BlockingQueue<Integer> queue) {
        for (int i = 0; i < 3; i++) {
            // 这里的变量类型不可以使用 int ,如果返回的是 null 则会抛出空指针异常
            Integer result = queue.poll();
            System.out.println(result);
        }
    }
7)、 poll( long  timeout TimeUnit  unit ) 方法:
/**
     * ArrayBlockingQueue的 poll() 方法 获取并队列的头元素,可以控制阻塞时间,
     * 在队列中没有元素时会一直等待(阻塞),直到队列中有新的可用元素,
     * 如果等待到指定时间队列中依然没有可用元素则返回 null。如果在等待时被中断会抛出InterruptedException
     * @param queue 长度为2的ArrayBlockingQueue
     */
    private static void testPoll2 (BlockingQueue<Integer> queue) {
        try {
            for (int i = 0; i < 3; i++) {
                Integer result = queue.poll(2L, TimeUnit.SECONDS);
                System.out.println(result);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
8)、 take() 方法:
/**
     * ArrayBlockingQueue的 take() 方法, 该方法会阻塞,
     * 如果队列里没有元素时就会阻塞,知道对立中有元素时则取队列的头元素
     * 如果在等待时中断会抛出 InterruptedException
     * @param queue 长度为2的ArrayBlockingQueue
     */
    private static void testTake(BlockingQueue<Integer> queue) {
        try {
            for (int i = 0; i < 3; i++) {
                Integer result = queue.take();
                System.out.println(result);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


猜你喜欢

转载自blog.csdn.net/wgs_93/article/details/80427720
今日推荐