<返回更多

Android多线程编程之详解:阻塞队列+线程池

2019-09-23    
加入收藏
Android多线程编程之详解:阻塞队列+线程池

 

一、阻塞队列简介

阻塞队列常用于生产者和消费者场景,生产者往往是往队列里添加元素的线程,消费者

是从队列里拿元素的线程吗,阻塞队列就是生产者存放元素的容器,是消费者拿元素的容器

1.常见阻塞场景

当前队列中没有数据的情况下,消费端的所有线程都会被自动阻塞(挂起),直到有数据放入队列

当队列种数据填充满的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中

有空的位置,线程被自动唤醒

2.BlockingQueue

2.1.放入数据:

2.2.获取数据:

 

二、JAVA中的阻塞队列

1.ArrayBlockingQueue:由数组结构组成的有界阻塞队列

他是用数组实现的有界阻塞队列,并按照先进先出的原则对元素进行排序,默认情况下不保证线程公平的访问队列,公平的访问队列就是指阻塞的所有生产者线程或消费者线程当队列不可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者先生产,先阻塞的消费者线程可以先从队列里获取元素,通常情况下为了保证公平性会降低吞吐量。

2.LinkedBlockingQueue:由链表结构组成的有限阻塞队列

他是基于链表的阻塞队列,同ArrayListBlockingQueue类似,此队列按照先进先出的原则对元素进行排序,其内部也会维持着一个数据缓冲队列,当生产者往队列中放入一个数据时,队列会从生产者手中获取数据并缓存在队列内部,而生产者立即返回,只有当队列缓冲区达到缓存容量的最大值时(可以指定该值),才会阻塞生产者线程,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之,对于消费者这段的处理也基于同样的原理,而LinkedBlockingQueue之所以能够高效的处理处理并发数据,还因为其对于生产者端和消费者端分别采用独立的锁来控制数据同步。

以上两个常用的阻塞队列,还有五种不再详细介绍。

下面分析ArrayBlockingQueue的源代码:

private static final long serialVersionUID = -817911632652898426L;
 final Object[] items;//阻塞队列维护的一个object类型的数组
 int takeIndex;//队首元素
 int putIndex;//队尾元素
 int count;//队列中的元素
 final ReentrantLock lock;//重入锁
 private final Condition notEmpty;//条件对象判断数组不是满的
 private final Condition notFull;//条件对象判断数组不是空的
 transient Itrs itrs;
 final int dec(int i) {
 return ((i == 0) ? items.length : i) - 1;
 }
 /**
 * Returns item at index i.
 */
 @SuppressWarnings("unchecked")
 final E itemAt(int i) {
 return (E) items[i];
 }
 /**
 * 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();
 }
 /**
 * Extracts element at current take position, advances, and signals.
 * Call only when holding lock.
 */
 private E dequeue() {
 // assert lock.getHoldCount() == 1;
 // assert items[takeIndex] != null;
 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;
 }
 //取元素
 public void put(E e) throws InterruptedException {
 Objects.requireNonNull(e);
 final ReentrantLock lock = this.lock;//锁
 lock.lockInterruptibly();
 try {
 while (count == items.length)
 notFull.await();//阻塞线程,等待notFull.signalAll()唤醒
 enqueue(e);
 } finally {
 lock.unlock();
 }
 }
 public E take() throws InterruptedException {
 final ReentrantLock lock = this.lock;
 lock.lockInterruptibly();
 try {
 while (count == 0)
 notEmpty.await();//阻塞线程,等待notEmpty.await()唤醒
 return dequeue();
 } finally {
 lock.unlock();
 }
 }

使用阻塞队列就无需考虑同步和线程间通信的问题。

public class VolatikeDemo {
 private int queueSize=10;
 private ArrayBlockingQueue<Integer> queue=new ArrayBlockingQueue<>(queueSize);
 public static void main(String args[]){
 VolatikeDemo demo=new VolatikeDemo();
 Consumer consumer=demo.new Consumer();
 Producer producer=demo.new Producer();
 consumer.start();
 producer.start();
 }
 class Consumer extends Thread{
 @Override
 public void run() {
 while(true){
 try {
 int res=queue.take();
 System.out.println(res);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 }
 }
 class Producer extends Thread{
 @Override
 public void run() {
 while(true){
 try {
 this.sleep(200);
 queue.put(1);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 }
 }
}
Android多线程编程之详解:阻塞队列+线程池

 

三、线程池

在编程中经常会使用线程来异步处理任务,但是每个线程的创建和销毁都需要一定的 开销。如果每次执行一个任务都需要打开一个新线程去执行,则这些线程的创建和销毁 将消耗大量的资源,并且线程都是各自为政的,很难对其进行控制,更何况还有一堆的 线程在执行,这时就需要线程池来对线程进行管理,在java 1.5中提供了Executor框架用于 把任务的提交和执行解耦,任务的提交交给Runnable或者Callable,而Executor框架用来 处理任务,Executor框架中的核心成员就是ThreadPoolExecutor,他是线程池的核心类。

1.ThreadPoolExecutor

可以通过创建ThreadPoolExecutor来创建一个线程池,ThreadPoolExecutor类一共有四个构造方法

其中拥有最多参数的构造方法:

ThreadPoolExecutor poolExecutor=new ThreadPoolExecutor(?,?,?,?,?);

2.线程池的处理流程和原理

Android多线程编程之详解:阻塞队列+线程池

 

2.1.提交任务后,线程池先判断线程数是否达到了核心线程数(corepoolSize)

如果还没有达到核心线程数,则创建核心线程处理任务,否则执行下一步。

2.2.线程池判断任务队列是否满了,如果没满,将任务加入任务队列,否则执行

下一步。

2.3.线程池判断线程数是否达到最大线程数,如果未达到,则创建非核心线程处理任务,

否则就执行饱和策略,默认会抛出RejectedExecutionExeception异常。

Android多线程编程之详解:阻塞队列+线程池

 

通过线程池的执行示意图我们可以看出,如果我们执行ThreadPoolExecutor的execute方法,

会遇到各种情况

3.四种常用的线程池

3.1.FixedThreadPool:他是可重用固定线程数的线程池,他的主要特点如下:

Android多线程编程之详解:阻塞队列+线程池

 

4.CacheThreadPool

4.1.CacheThreadPool:他是一个根据需要创建线程的线程池,他的主要特点如下:

Android多线程编程之详解:阻塞队列+线程池

 

Android多线程编程之详解:阻塞队列+线程池

 

最后

如果你看到了这里,觉得文章写得不错就给个赞呗!欢迎大家评论讨论!如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足,定期免费分享技术干货。喜欢的小伙伴可以关注一下哦。谢谢!

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>