BlockingQueue讲解:
1 BlockingQueue概述:
BlocingQueue,顾名思义:<font color=red>阻塞队列</font>.BlockingQueue是在java.util.concurrent下的,因此不难理解,BlockingQueue是为了解决多线程中数据高效安全传输而提出的。
阻塞队列所谓的“阻塞”,指的是<font color=red>某些情况下线程会挂起(即阻塞),一旦条件满足,被挂起的线程又会自动给唤醒。</font>使用BlockingQueue,不需要关心什么时候需要阻塞线程,什么时候需哟啊唤醒线程,这些内容BlockingQueue都已经做好了。
2 BlockingQueue中的方法:
方法摘要 boolean add(E e) 将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException。 boolean contains(Object o) 如果此队列包含指定元素,则返回 true。 int drainTo(Collection<? super E> c) 移除此队列中所有可用的元素,并将它们添加到给定 collection 中。 int drainTo(Collection<? super E> c, int maxElements) 最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定 collection 中。 boolean offer(E e) 将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果当前没有可用的空间,则返回 false。 boolean offer(E e, long timeout, TimeUnit unit) 将指定元素插入此队列中,在到达指定的等待时间前等待可用的空间(如果有必要)。 E poll(long timeout, TimeUnit unit) 获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。 void put(E e) 将指定元素插入此队列中,将等待可用的空间(如果有必要)。 int remainingCapacity() 返回在无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的附加元素数量;如果没有内部限制,则返回 Integer.MAX_VALUE。 boolean remove(Object o) 从此队列中移除指定元素的单个实例(如果存在)。 E take() 获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。
3 ArrayBlockingQueue
基于数组的阻塞队列,<font color=red>必须制定队列大小</font>。ArrayBlocingQueue中只有一个ReetrantLock对象,这意味着生产者和消费者无法并行。另外创建ArrayBlockingQueue时,可以指定ReetrantLock是否为公平锁,默认采用非公平锁。
/** 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
/** 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();
基于链表的阻塞队列,和ArrayBlockingQueue差不多。不过LinkedBlockingQueue如果不指定队列容量大小,会默认一个类似无限大小的容量,之所以说是类似是因为这个无限大小是Integer.MAX_VALUE,这么说就好理解ArrayBlockingQueue为什么必须要制定大小了,如果ArrayBlockingQueue不指定大小的话就用Integer.MAX_VALUE,那将造成大量的空间浪费,但是基于链表实现就不一样的,一个一个节点连起来而已。另外,LinkedBlockingQueue生产者和消费者都有自己的锁(见下面的代码),这意味着生产者和消费者可以"同时"运行。
5 SynchronousQueue
一种没有缓冲的等待队列。
什么叫做没有缓存区:
ArrayBlockingQueue中有:
/** The queued items */private final E[] items;
数组用以存储队列
LinkedBlockingQueue:
/** * Linked list node class */static class Node<E> { /** The item, volatile to ensure barrier separating write and read */ volatile E item; Node<E> next; Node(E x) { item = x; }}
将队列以链表形式连接。
生产者/消费者操作数据实际上都是通过这两个"中介"来操作数据的,但是SynchronousQueue则是生产者直接把数据给消费者(消费者直接从生产者这里拿数据),好像又回到了没有生产者/消费者模型的老办法了。换句话说,每一个插入操作必须等待一个线程对应的移除操作。SynchronousQueue又有两种模式:
1、公平模式
采用公平锁,并配合一个FIFO队列(Queue)来管理多余的生产者和消费者
2、非公平模式
采用非公平锁,并配合一个LIFO栈(Stack)来管理多余的生产者和消费者,这也是SynchronousQueue默认的模式
利用BlockingQueue实现生产者消费者模型:
public static void main(String[] args){ final BlockingQueue<String> bq = new ArrayBlockingQueue<String>(10); Runnable producerRunnable = new Runnable() { int i = 0; public void run() { while (true) { try { System.out.println("我生产了一个" + i++); bq.put(i + ""); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Runnable customerRunnable = new Runnable() { public void run() { while (true) { try { System.out.println("我消费了一个" + bq.take()); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread producerThread = new Thread(producerRunnable); Thread customerThread = new Thread(customerRunnable); producerThread.start(); customerThread.start();}运行结果:我生产了一个0 2 我消费了一个1 3 我生产了一个1 4 我生产了一个2 5 我消费了一个2 6 我生产了一个3 7 我生产了一个4 8 我生产了一个5 9 我消费了一个310 我生产了一个611 我生产了一个712 我生产了一个813 我消费了一个414 我生产了一个915 我生产了一个1016 我生产了一个1117 我消费了一个518 我生产了一个1219 我生产了一个1320 我生产了一个1421 我消费了一个622 我生产了一个1523 我生产了一个1624 我消费了一个725 我生产了一个1726 我消费了一个827 我生产了一个18
分两部分来看输出结果:
1、第1行~第23行。这块BlockingQueue未满,所以生产者随便生产,消费者随便消费,基本上都是生产3个消费1个,消费者消费速度慢
2、第24行~第27行,从前面我们可以看出,生产到16,消费到6,说明到了ArrayBlockingQueue的极限10了,这时候没办法,生产者生产一个ArrayBlockingQueue就满了,所以不能继续生产了,只有等到消费者消费完才可以继续生产。所以之后的打印内容一定是一个生产者、一个消费者
这就是前面一章开头说的<font color=red>"通过平衡生产者和消费者的处理能力来提高整体处理数据的速度"</font>,这给例子应该体现得很明显。另外,也不要担心非单一生产者/消费者场景下的系统假死问题,缓冲区空、缓冲区满的场景BlockingQueue都是定义了不同的Condition,所以不会唤醒自己的同类。
共同学习,写下你的评论
评论加载中...
作者其他优质文章