多线程进阶(六)-- 阻塞队列BlockingQueue

队列Queue:

队列,是一种数据结构。除了优先级队列和LIFO队列外,队列都是以FIFO(先进先出)的方式对各个元素进行排序的。无论使用哪种排序方式,队列的头都是调用remove()或poll()移除元素的。在FIFO队列中,所有新元素都插入队列的末尾。

Queue中的方法:

Queue中的方法不难理解,6个,每2对是一个也就是总共3对。看一下JDK API就知道了:
在这里插入图片描述
注意一点就好,Queue通常不允许插入Null,尽管某些实现(比如LinkedList)是允许的,但是也不建议。

阻塞队列BlockingQueue

在这里插入图片描述

阻塞与队列:

写入:如果队列满了,就必须阻塞等待
取:如果队列是空的,就必须阻塞等待生产

BlockingQueue概述

在这里插入图片描述

只讲BlockingQueue,因为BlockingQueue是Queue中的一个重点,并且通过BlockingQueue我们再次加深对于生产者/消费者模型的理解。其他的Queue都不难,通过查看JDK API和简单阅读源码完全可以理解他们的作用。

BlockingQueue,顾名思义,阻塞队列。BlockingQueue是在java.util.concurrent下的,因此不难理解,BlockingQueue是为了解决多线程中数据高效安全传输而提出的。

多线程中,很多场景都可以使用队列实现,比如经典的生产者/消费者模型,通过队列可以便利地实现两者之间数据的共享,定义一个生产者线程,定义一个消费者线程,通过队列共享数据就可以了。

当然现实不可能都是理想的,比如消费者消费速度比生产者生产的速度要快,那么消费者消费到 一定程度上的时候,必须要暂停等待一下了(使消费者线程处于WAITING状态)。BlockingQueue的提出,就是为了解决这个问题的,他不用程序员去控制这些细节,同时还要兼顾效率和线程安全。

阻塞队列所谓的"阻塞",指的是某些情况下线程会挂起(即阻塞),一旦条件满足,被挂起的线程又会自动唤醒。使用BlockingQueue,不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,这些内容BlockingQueue都已经做好了

BlockingQueue的四组API
方式抛出异常有返回值,不抛出异常阻塞等待(阻塞队列特有)超时等待
添加addofferputoffer(,,)
移除removepolltakepoll(,)
判断队列首elementpeek
1. 抛出异常:
添加add():
public class Test1 {
    public static void main(String[] args) {
        test1();
    }

    public static void test1() {
        ArrayBlockingQueue queue = new ArrayBlockingQueue(3);

        System.out.println(queue.add("a"));
        System.out.println(queue.add("b"));
        System.out.println(queue.add("c"));
        System.out.println(queue.add("d"));
    }
}

结果:队列已满异常

true
true
true
Exception in thread "main" java.lang.IllegalStateException: Queue full
	at java.base/java.util.AbstractQueue.add(AbstractQueue.java:98)
	at java.base/java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:326)
	at com.yhx.juc.BlockQueue.Test1.put(Test1.java:21)
	at com.yhx.juc.BlockQueue.Test1.main(Test1.java:12)
取出remove():

代码:

public static void test1() {
    ArrayBlockingQueue queue = new ArrayBlockingQueue(3);

    System.out.println(queue.add("a"));
    System.out.println(queue.add("b"));
    System.out.println(queue.add("c"));
    // System.out.println(queue.add("d"));
    System.out.println("=======");
    System.out.println(queue.remove());
    System.out.println(queue.remove());
    System.out.println(queue.remove());
    System.out.println(queue.remove());
}

结果:队列为空异常

true
true
true
=======
a
b
c
Exception in thread "main" java.util.NoSuchElementException
	at java.base/java.util.AbstractQueue.remove(AbstractQueue.java:117)
	at com.yhx.juc.BlockQueue.Test1.test1(Test1.java:26)
	at com.yhx.juc.BlockQueue.Test1.main(Test1.java:12)
判断队列首element():
public static void test1() {
    ArrayBlockingQueue queue = new ArrayBlockingQueue(3);

    System.out.println(queue.add("a"));
    System.out.println(queue.add("b"));
    System.out.println(queue.add("c"));
    // System.out.println(queue.add("d"));
    System.out.println("=======");
    System.out.println(queue.remove());
    System.out.println(queue.remove());
    System.out.println(queue.element());
    System.out.println(queue.remove());
    System.out.println(queue.element());
    // System.out.println(queue.remove());
}

结果:

true
true
true
=======
a
b
c
c
Exception in thread "main" java.util.NoSuchElementException
	at java.base/java.util.AbstractQueue.element(AbstractQueue.java:136)
	at com.yhx.juc.BlockQueue.Test1.test1(Test1.java:27)
	at com.yhx.juc.BlockQueue.Test1.main(Test1.java:12)
2.不抛出异常:
添加offer():
public static void test2() {
    ArrayBlockingQueue queue = new ArrayBlockingQueue(3);

    System.out.println(queue.offer("a"));
    System.out.println(queue.offer("b"));
    System.out.println(queue.offer("c"));
    System.out.println(queue.offer("d"));
}

结果:可以看到无法添加的时候,返回false

true
true
true
false
取出poll:

代码:

public static void test2() {
    ArrayBlockingQueue queue = new ArrayBlockingQueue(3);

    System.out.println(queue.offer("a"));
    System.out.println(queue.offer("b"));
    System.out.println(queue.offer("c"));
    //System.out.println(queue.offer("d"));
    System.out.println("=======");
    System.out.println(queue.poll());
    System.out.println(queue.poll());
    System.out.println(queue.poll());
    System.out.println(queue.poll());
}

结果:取不到返回null

true
true
true
=======
a
b
c
null
判断队列首peek:
public static void test2() {
    ArrayBlockingQueue queue = new ArrayBlockingQueue(3);

    System.out.println(queue.offer("a"));
    System.out.println(queue.offer("b"));
    System.out.println(queue.offer("c"));
    System.out.println("=======");
    System.out.println(queue.poll());
    System.out.println(queue.poll());
    System.out.println(queue.peek());
    System.out.println(queue.poll());
    System.out.println(queue.peek());
}

结果:

true
true
true
=======
a
b
c
c
null
3. 阻塞(阻塞队列特有方法)
添加put:
public static void test3() throws InterruptedException {
    ArrayBlockingQueue queue = new ArrayBlockingQueue(3);

    queue.put("a");
    queue.put("b");
    queue.put("c");
    queue.put("d"); // 队列没有位置了,一直阻塞
}

结果:可以看到程序一直在执行,没有停止,因为d在阻塞等待插入
在这里插入图片描述

取出take():
public static void test3() throws InterruptedException {
    ArrayBlockingQueue queue = new ArrayBlockingQueue(3);

    queue.put("a");
    queue.put("b");
    queue.put("c");
    // queue.put("d"); // 队列没有位置了,一直阻塞
    queue.take();
    queue.take();
    queue.take();
    queue.take(); // 无法取出,一直阻塞
}

结果:还是向上次那样,程序一直在执行,没有停止,处于阻塞状态

4.超时退出
添加offer(E e, long timeout, TimeUnit unit):

设置阻塞2秒还拿不到就退出:

public static void test4() throws InterruptedException {
    ArrayBlockingQueue queue = new ArrayBlockingQueue(2);

    System.out.println("插入啦" + System.currentTimeMillis());
    queue.offer("a");
    queue.offer("b");
    queue.offer("c", 2, TimeUnit.SECONDS); //
    System.out.println("退出啦" + System.currentTimeMillis());
}

结果:

插入啦1595096226652
退出啦1595096228667
取出E poll(long timeout, TimeUnit unit):
public static void test4() throws InterruptedException {
    ArrayBlockingQueue queue = new ArrayBlockingQueue(2);

    System.out.println("插入啦" + System.currentTimeMillis());
    queue.offer("a");
    queue.offer("b");
    queue.offer("c", 2, TimeUnit.SECONDS); //
    System.out.println("退出啦" + System.currentTimeMillis());
    System.out.println("=======");
    System.out.println("我取啦" + System.currentTimeMillis());
    queue.poll();
    queue.poll();
    queue.poll(2, TimeUnit.SECONDS);
    System.out.println("又退啦" + System.currentTimeMillis());
}

结果:

插入啦1595096429562
退出啦1595096431576
=======
我取啦1595096431576
又退啦1595096433579

ArrayBlockingQueue:

上述的代码都是使用的ArrayBlockingQueue类来实现的。

他是一个基于数组的阻塞队列,必须指定队列大小。比较简单。ArrayBlockingQueue中只有一个ReentrantLock对象,这意味着生产者和消费者无法并行运行(见下面的代码)。另外,创建ArrayBlockingQueue时,可以指定ReentrantLock是否为公平锁,默认采用非公平锁。

 /** Main lock guarding all access */
 final ReentrantLock lock;

 /** Condition for waiting takes */
 private final Condition notEmpty;

 /** Condition for waiting puts */
 private final Condition notFull;

LinkedBlockingQueue:

基于链表的阻塞队列,和ArrayBlockingQueue差不多。不过LinkedBlockingQueue如果不指定队列容量大小,会默认一个类似无限大小的容量,之所以说是类似是因为这个无限大小是Integer.MAX_VALUE,这么说就好理解ArrayBlockingQueue为什么必须要制定大小了,如果ArrayBlockingQueue不指定大小的话就用Integer.MAX_VALUE,那将造成大量的空间浪费,但是基于链表实现就不一样的,一个一个节点连起来而已。另外,LinkedBlockingQueue生产者和消费者都有自己的锁(见下面的代码),这意味着生产者和消费者可以"同时"运行。

/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();

/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();

/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();

/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();

SynchronousQueue同步队列:

一种没有缓冲的等待队列。
什么叫做没有缓冲区,ArrayBlocking中有数组用于存储队列,LinkedBlockingQueue有链表村村队列:

/** The queued items  */
private final E[] items;
static class Node<E> {
    E item;

    /**
     * One of:
     * - the real successor Node
     * - this Node, meaning the successor is head.next
     * - null, meaning there is no successor (this is the last node)
     */
    Node<E> next;

    Node(E x) { item = x; }
}

生产者/消费者操作数据实际上都是通过这两个"中介"来操作数据的,但是SynchronousQueue则是生产者直接把数据给消费者(消费者直接从生产者这里拿数据),好像又回到了没有生产者/消费者模型的老办法了。换句话说,每一个插入操作必须等待一个线程对应的移除操作

放进入一个东西,必须等这个东西被取出去之后,才能再放。

SynchronousQueue的两种模式:
  1. 公平模式
    采用公平锁,并配合一个FIFO队列(Queue)来管理多余的生产者和消费者
  2. 非公平模式
    采用非公平锁,并配合一个LIFO栈(Stack)来管理多余的生产者和消费者,这也是SynchronousQueue默认的模式
示例:
public static void main(String[] args) {
    BlockingQueue<Integer> queue = new SynchronousQueue<>(); // 同步队列

    new Thread(() -> {
        try {
            for (int i = 1; i <= 3; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + "==put" + i);
                queue.put(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();

    new Thread(() -> {
        try {
            for (int i = 1; i <= 3; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + "==get" + queue.take());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

结果:

Thread-0==put1
Thread-1==get1
Thread-0==put2
Thread-1==get2
Thread-0==put3
Thread-1==get3

阻塞队列实现生产者消费者模型:

public static void main(String[] args) {
    AtomicInteger a = new AtomicInteger(0);
    final ArrayBlockingQueue queue = new ArrayBlockingQueue(5);

    new Thread(() -> {
        for (int i = 1; i < 1000; i++) {
            try {
                queue.put(i);
                System.out.println("生产:" + i);
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
    new Thread(() -> {
        while (true) {
            try {
                System.out.println("消费:" + queue.take());
                TimeUnit.SECONDS.sleep(8);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

结果:

消费:1
生产:1
生产:2
生产:3
生产:4
生产:5
生产:6
消费:2
生产:7
消费:3
生产:8
消费:4
生产:9
消费:5
生产:10

解释一下:

  1. 先打印消费,是因为异步的关系,消费的输出语句先执行了,接着生产1消费1,相当于此时队列中没有数据;
  2. 接着由于消费时间较长,生产者连续生产了5个商品,接着不再生产,等待消费者过来消费
  3. 消费者消费了2,接着生产者生产,生产了一个之后队列又满了,等待消费者消费;
  4. 以此反复,生产者生产一个等待被消费,消费者消费后生产者再生产。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值