Android中阻塞队列的应用有哪些

阻塞队列在 Android 中有很多应用,比如:

  1. 线程池:线程池任务的执行就是基于一个阻塞队列,如果线程池任务已满,则任务需要等待阻塞队列中的其他任务完成。
  2. Handler 消息队列:Handler 的消息队列也是一种阻塞队列。handler发送消息时,首先将消息加入到消息队列中,在空闲状态下 MessageQueue 队列是阻塞的状态,直到队列不为空,Looper 开始轮询 MessageQueue 里面的 Message。

阻塞队列支持两个核心方法,分别为:

  • put(E e):将元素插入队尾,支持阻塞式插入,在容量无限制的情况下一直等待直到队列有空闲位置。
  • take(): 获取且移除此队列的头部,在队列为空时,阻塞等待这个头部可用,并返回被取出的元素。

在 Java 中,阻塞队列的实现类有 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue 等等。这些队列通过使用 Lock 和 Condition 来保证队列的线程安全。

阻塞队列的原理

是当阻塞队列中没有数据的时候,读线程将被阻塞;当阻塞队列已满的时候,写线程将被阻塞。阻塞队列通过内部锁来控制读和写操作的相互访问以及有序性,保证在多线程并发的时候,执行插入或读取操作是广泛可接受的,而不会引起任何问题。

线程池是维护一个工作线程的线程容器,用于优化线程的创建和销毁带来的开销。下面简单介绍一下线程池的底层实现原理。

线程池执行流程

  1. 在初始化时,线程池会通过ThreadPoolExecutor构造函数赋值并创建核心线程池;
  2. execute()方法将任务存放到阻塞队列中,如果有空闲线程,则取出最先进入队列的任务开始执行,否则加入等待中的队列中;
  3. 当队列长度达到阈值时,不再继续增加,此时若仍有新任务提交,则以上空闲线程处理任务;若添加的线程数仍超过最大线程数时,则拒绝该任务。

参数说明:

以下是线程池最基本的四类参数。

  • 核心线程数(corePoolSize):线程池中的基本线程数。当任务提交后,分类讨论:

    • 若线程池中的线程数小于 corePoolSize,那么即使线程池中还有空余的线程,也会直接添加一个新线程去处理当前的任务;
    • 若线程池已经有了 corePoolSize 个线程,那么任务就被加入阻塞队列进行缓冲,等待有空闲的线程来处理;
    • 一次性只能创建 corePoolSize 个线程。
  • 最大线程数(maximumPoolSize):线程池中允许存在的最大线程数。当缓冲队列满时,对于大于 corePoolSize 的任务将会此项启动更多的 Thread 来处理用户请求,针对有界和无界不同表现。如有等待队列功能满了后会执行RejectedExecutionException。在我看来这个配置很鸡肋,往往在它小于corepoolsize时候有表现,大于情况视大于多少对线程池复用机制上友好而已。有不对的欢迎评论区讨论下;

  • 队列容量(workQueue):等待任务的队列容量。在调用 execute() 方法时,阻塞队列可以用以下三种类型之一:

    • FIFO(先进先出)队列,使用java.util.concurrent.LinkedBlockingQueue,默认无界限大小。因此 newFixedThreadPool() 和 newSingleThreadExecutor() 的线程数最多也可能会达到 Integer.MAX_VALUE,会导致OOM等问题。但你可指定其大小。
    • LIFO(后进先出)队列,使用java.util.concurrent.LinkedBlockingDeque,特点是新任务插到队尾上,但在 JDK7 中不再使用这个队列,因为它是不符合 Java 应用程序的行为模式的;
    • 优先级队列,使用java.util.concurrent.PriorityBlockingQueue 类实现,按照元素等级来排序,低等级对象会先被获取,对于高等级的任务可以提前执行。
  • 空闲线程销毁时间(keepAliveTime):线程池中没有任务执行时,即空闲状态下的线程没事可做,这些空闲的线程会自动的销毁,首先由coreThreadHandle(正常获得锁的线程)完成剩下工作,然后 wait coreHandle 释放锁资源,休眠到内部队列不为空或超时返回,如果休眠超时则表示该线程超时了,在低于 corePoolSize 的情况下 除非设置allowCoreThreadTimout=true,否则永远不会发生;

实例代码

public class TestThreadPoll implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行...");
}
} public static void main(String[] args) {
// 创建一个基本的线程池
ExecutorService executor = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) {
Runnable task = new TestThreadPoll();
executor.execute(task);
}
executor.shutdown();
}

上面的创建了一个基本的线程池,并进行了线程数的添加。

线程池排队机制

当线程池中所有的线程都在执行任务时,如果新提交的任务需要被执行,那么这个新的任务会进入到一个阻塞队列中排队等待。而ThreadPoolExecutor中有四种常见的阻塞队列:SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue、PriorityBlockingQueue。

其中SynchronousQueue是没有任何储存功能的阻塞队列,它只负责一个任务的传递,即前一个任务将后一个任务提交给线程池后直接结束,而不是进入等待队列。由于该队列没有储存空间,所以当一个线程提交任务时,该队列会寻找与之对应的另一个线程来接收这个任务。如果此时线程池处于饱和状态,这个任务就有可能被丢弃。

LinkedBlockingQueue则是一个带储存功能的无界队列,也就是说,这个队列可以一直往里添加新的元素。因此,它的长度限制仅仅是由内存大小来控制,如果你的阻塞队列需要存储非常多的任务,那么最好选用这个类型的阻塞队列。

ArrayBlockingQueue也是一个带储存功能的有界队列,它的长度是预设好的,如果你试图往这个队列中添加超过预设长度的任务,那么新增的任务就必须要等待其他任务完成或者被移除后才能加入队列了,换而言之,在这种队列中,无法满足新任务的请求时会出现阻塞情况。

PriorityBlockingQueue则是一个支持优先级排序的无界队列,也就是说,任务可以通过实现Comparable接口来设置一个队列中的优先级关系。当使用优先级队列时,可以为不同的任务设置不同的优先级,程序会按照任务的优先级顺序执行具体的任务。

自实现一个线程池实例

以下是一个简单的自实现线程池示例:

public class MyThreadPool {
private final BlockingQueue<Runnable> workQueue;
private final WorkerThread[] workers; public MyThreadPool(int nThreads) {
this.workQueue = new LinkedBlockingQueue<>();
this.workers = new WorkerThread[nThreads]; for (int i = 0; i < nThreads; i++) {
workers[i] = new WorkerThread();
workers[i].start();
}
} public void execute(Runnable task) {
synchronized (workQueue) {
workQueue.add(task);
workQueue.notify();
}
} private class WorkerThread extends Thread {
@Override
public void run() {
while(true) {
Runnable task;
synchronized (workQueue) {
while (workQueue.isEmpty()) {
try {
workQueue.wait();
} catch (InterruptedException e) {
return;
}
}
task = workQueue.poll();
}
try {
task.run();
} catch(Throwable t) {
// error handling code
}
}
}
}
}

这是一个最简单的线程池实现,其核心思想就是:在.WorkerThread类中定义了一个死循环,负责从任务队列中取出任务并执行。如果目前没有任务,则等待新任务到来。

使用时初始化线程池:

MyThreadPool pool = new MyThreadPool(2);

之后将需要做的任务加入线程池中:

pool.execute(new Runnable() {
@Override
public void run() {
// 执行具体的任务代码
}
});

至此,自实现的线程池实例就完成了。

execute框架解读

execute()是Java中Executor接口的一个方法,它在执行传递给它的Runnable任务时使用。Executor框架提供了一种将任务提交给线程池以异步执行的方式。

当我们想要某段代码在异步环境中运行,又不需要知道每个任务何时完成时(所需时间可能会非常不同)的情况下,就可以使用这个框架。该框架管理和复用线程,以避免耗费创建他们的代价,并且在执行完任务后返回线程到线程池。

execute()方法的目的是在默认池中安排一个任务来执行,由于这个方法只是将任务提交给线程池并立即返回,因此不能保证任务已经执行。这个方法只有在向线程池中添加任务时需要使用,例如对于没有返回值的简单操作或前置条件检查。

以下是execute()的方法语法:

void execute(Runnable command);

参数:

  • command:Runnable对象,该接口定义了需要在线程上执行的任务。

实现:

该方法直接将任务提交给主执行器以异步执行。然后返回而不等待任务的结束。如果需要使用结果,则调用submit方法。

//newFixedThreadPool 被重载用来覆盖默认设置,指定核心线程数和最大线程数相同。
//此示例将线程池大小限制为 5 个工作线程。
ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) {
Runnable worker = new ExampleRunnable("" + i);
executor.execute(worker);
}
// 当所有可执行任务都完成后关闭线程池
executor.shutdown();
// 阻塞当前线程直到关闭操作完成
while (!executor.isTerminated()) { } System.out.println("Finished all threads");

以上代码中,新的线程池有默认的限制,而不需要明确地声明线程池的大小或能力 。

线程池固定大小,当队列中没有可以在工作线程上执行的任务时(即阻止列表为空),则该任务将继续等待直到可用。

在每个可用工作线程上使用while循环反复启动一个Runnable任务并让其运行到池的关闭标志被设置

线程池被关闭之后,isTerminated()方法返回true,并退出while循环。

ThreadPool实现机制的更多相关文章

  1. 用惯了Task,你应该也需要了解它的内部调度机制TaskScheduler

    平时我们在用多线程开发的时候少不了Task,确实task给我们带来了巨大的编程效率,在Task底层有一个TaskScheduler,它决定了task该如何执行,而在 .net framework中有两 ...

  2. Tomcat线程池,更符合大家想象的可扩展线程池

    因由 说起线程池,大家可能受连接池的印象影响,天然的认为,它应该是一开始有core条线程,忙不过来了就扩展到max条线程,闲的时候又回落到core条线程,如果还有更高的高峰,就放进一个缓冲队列里缓冲一 ...

  3. C#三种定时器

    三个定时器分别是 实现按用户定义的时间间隔引发事件的计时器.此计时器最宜用于 Windows 窗体应用程序中,并且必须在窗口中使用. System.Windows.Forms.Timer 提供以指定的 ...

  4. C#TaskScheduler 任务调度器的原理

    什么是TaskScheduler? SynchronizationContext是对"调度程序(scheduler)"的通用抽象.个别框架会有自己的抽象调度程序,比如System. ...

  5. Java threadpool机制深入分析

    简介 在前面的一篇文章里我对java threadpool的几种基本应用方法做了个总结.Java的线程池针对不同应用的场景,主要有固定长度类型.可变长度类型以及定时执行等几种.针对这几种类型的创建,j ...

  6. [.net 多线程]ThreadPool的安全机制

    ThreadPool类,有两个方法我们没有用到,UnsafeQueueUserWorkItem 和UnsafeRegisterWaitForSingleObject. 为了完全理解这些方法,首先,我们 ...

  7. 二 Java利用等待/通知机制实现一个线程池

    接着上一篇博客的 一Java线程的等待/通知模型 ,没有看过的建议先看一下.下面我们用等待通知机制来实现一个线程池 线程的任务就以打印一行文本来模拟耗时的任务.主要代码如下: 1  定义一个任务的接口 ...

  8. 分析.Net里线程同步机制

    我 们知道并行编程模型两种:一种是基于消息式的,第二种是基于共享内存式的. 前段时间项目中遇到了第二种 使用多线程开发并行程序共享资源的问题 ,今天以实际案例出发对.net里的共享内存式的线程同步机制 ...

  9. ThreadPool原理介绍

    public class ThreadPoolExecutorextends AbstractExecutorService 一个 ExecutorService,它使用可能的几个池线程之一执行每个提 ...

  10. quartz集群调度机制调研及源码分析---转载

    quartz2.2.1集群调度机制调研及源码分析引言quartz集群架构调度器实例化调度过程触发器的获取触发trigger:Job执行过程:总结:附: 引言 quratz是目前最为成熟,使用最广泛的j ...

随机推荐

  1. 阿里云 rocketMq 延时消息

    初始化消费者和生产者 生产者 设置rocketmq的accesskey 和secretkey 以及rocketmq的 binder server. 首先 编辑一个配置类,将关于配置rocketmq的东 ...

  2. 7种实现web实时消息推送的方案

    做了一个小破站,现在要实现一个站内信web消息推送的功能,对,就是下图这个小红点,一个很常用的功能. 不过他还没想好用什么方式做,这里我帮他整理了一下几种方案,并简单做了实现. 什么是消息推送(pus ...

  3. 20192305 王梓全Python程序设计实验四报告

    20192305 王梓全Python程序设计实验四报告 课程:<Python程序设计> 班级: 1923 姓名: 王梓全 学号:20192305 实验教师:王志强 实验日期:2021年6月 ...

  4. pycharm导入第三方包

  5. mysql授权、导入等基本操作

    1.授权: mysqladmin -uroot password rootpwd mysql -uroot -prootpwd mysql -e "INSERT INTO user (Hos ...

  6. js字符串搜索

  7. excel制作表格

    我们这个表格举例: 1. 序号列自动生成序号.选中数字"1"所在的单元格,将鼠标停留在图片中标红的区域向下拖动即可                                 ...

  8. element的el-table使用模板插槽在火狐和IE无法显示tooltip(浏览器兼容)

    el-table中使用show-overflow-tooltip属性,配合tooltip出现的浏览器兼容性问题 el-table中使用show-overflow-tooltip属性内容过长被隐藏时显示 ...

  9. apt-get install 出现could not open lock file /var/lib/dpkg/lock错误问题

    apt-get install 经常出现 could not open lock file /var/lib/dpkg/lock -open 错误问题 一种解决办法 1.切换到root用户 su ~ ...

  10. 删除oracle

    完全卸载oracle11g步骤: 1. 开始->设置->控制面板->管理工具->服务 停止所有Oracle服务. 2. 开始->程序->Oracle - OraHo ...