android 多线程并发处理,Android并发学习之阻塞队列

简介

多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。

BlockingQueue很好地解决了上述问题,BlockingQueue即阻塞队列,它是一个接口,它的实现类有ArrayBlockingQueue、DelayQueue、 LinkedBlockingDeque、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等,它们的区别主要体现在存储结构上或对元素操作上的不同。

25dddcb4f0853b9655a8bf30f47fbac1.png

常用方法

public interface BlockingQueue extends Queue{

//往队列尾部添加元素,如果BlockingQueue可以容纳,则返回true,否则抛出异常

boolean add(E e);

//移除元素,如果有这个元素则就回true,否则抛出异常

boolean remove(Object o);

//往队列尾部添加元素,如果BlockingQueue可以容纳则返回true,否则返回false.

//如果是往限定了长度的队列中设置值,推荐使用offer()方法。

boolean offer(E e);

//和上面的方法差不多,不过如果队列满了可以阻塞等待一段时间

boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;

//取出头部对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null

E poll(long timeout, TimeUnit unit) throws InterruptedException;

//往队列尾部添加元素,如果没有空间,则调用此方法的线程被阻塞直到有空间再继续.

void put(E e) throws InterruptedException;

//取出头部对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止

E take() throws InterruptedException;

//剩余容量,超出此容量,便无法无阻塞地添加元素

int remainingCapacity();

//判断队列中是否拥有该值。

boolean contains(Object o);

//一次性从BlockingQueue获取所有可用的数据对象,可以提升获取数据效率

int drainTo(Collection super E> c);

//和上面的方法差不多,不过限制了最大取出数量

int drainTo(Collection super E> c, int maxElements);

}

复制代码

源码简析

我们以ArrayBlockingQueue为例分析下上述方法:

offer(E e)

public boolean offer(E e){

Objects.requireNonNull(e);

final ReentrantLock lock = this.lock;

lock.lock();

try {

if (count == items.length)

return false;

else {

enqueue(e);

return true;

}

} finally {

lock.unlock();

}

}

private void enqueue(E x){

final Object[] items = this.items;

items[putIndex] = x;

if (++putIndex == items.length) putIndex = 0;

count++;

notEmpty.signal();

}

复制代码

offer操作如上,代码比较简单,可见阻塞队列是通过可重入保证线程安全。enqueue方法也说明了ArrayBlockingQueue是通过数组的形式存储数据的。如果队列满了直接会返回false,不会阻塞线程。

put(E e)

public void put(E e) throws InterruptedException {

Objects.requireNonNull(e);

final ReentrantLock lock = this.lock;

lock.lockInterruptibly();

try {

while (count == items.length)//队列满了,一直阻塞在这里

notFull.await();

enqueue(e);

} finally {

lock.unlock();

}

}

复制代码

因为put方法在队列已满的情况下会阻塞线程,take、poll等方法会调用dequeue方法出列,从而调用notFull.signal(),从而唤醒阻塞在put方法中线程去继续进行入列操作:

take()

public E take() throws InterruptedException{

final ReentrantLock lock = this.lock;

lock.lockInterruptibly();

try {

while (count == 0)

notEmpty.await();

return dequeue();

} finally {

lock.unlock();

}

}

private E dequeue(){

final Object[] items = this.items;

@SuppressWarnings("unchecked")

E x = (E) items[takeIndex];

items[takeIndex] = null;

if (++takeIndex == items.length) takeIndex = 0;

count--;

if (itrs != null)

itrs.elementDequeued();

notFull.signal();

return x;

}

复制代码

poll(long timeout, TimeUnit unit)

从对头取出一个元素:如果数组不空,出队;如果数组已空且已经超时,返回null;如果数组已空则进入等待,直到被唤醒或超时:

public E poll(long timeout, TimeUnit unit) throws InterruptedException {

long nanos = unit.toNanos(timeout);将时间转换为纳秒

final ReentrantLock lock = this.lock;

lock.lockInterruptibly();

try {

while (count == 0) {//队列为空

if (nanos <= 0L)

return null;

//阻塞指定时间,enqueue()方法会调用notEmpty.signal()唤醒进行poll操作的线程

nanos = notEmpty.awaitNanos(nanos);

}

return dequeue();

} finally {

lock.unlock();

}

}

复制代码

参考文章和扩展阅读

虽然只讲了阻塞队列,但涉及了ReentrantLock、中断、Condition等知识点,如果不清楚的话可以看下下面的几篇文章:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值