Android线程思考

  • View.post(Runnable r)

线程池


什么是线程池?

线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。 如果每个请求都创建一个线程去处理,那么服务器的资源很快就会被耗尽,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。 java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池

为什么要使用线程池?

创建线程和销毁线程的花销是比较大的,这些时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程,再加上业务工作线程,消耗系统资源的时间,可能导致系统资源不足。(我们可以把创建和销毁的线程的过程去掉)

多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。 假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。

如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。 一个线程池包括以下四个基本组成部分:

  1. 线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;

  2. 工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;

  3. 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;

  4. 任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

线程池有什么作用?

线程池作用就是限制系统中执行线程的数量

  1. 提高效率 创建好一定数量的线程放在池中,等需要使用的时候就从池中拿一个,这要比需要的时候创建一个线程对象要快的多。

  2. 方便管理 可以编写线程池管理代码对池中的线程同一进行管理,比如说启动时有该程序创建100个线程,每当有请求的时候,就分配一个线程去工作,如果刚好并发有101个请求,那多出的这一个请求可以排队等候,避免因无休止的创建线程导致系统崩溃。

线程池原理

Java通过Executors提供四种线程池

  • CachedThreadPool():可缓存线程池。如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。比较适合处理执行时间比较小的任务

  • FixedThreadPool():定长线程池。可控制线程最大并发数,超出的线程会在队列中等待。可以用于已知并发压力的情况下,对线程数做限制。

  • ScheduledThreadPool():定时线程池。支持定时及周期性任务执行。适用于需要多个后台线程执行周期任务的场景

  • SingleThreadExecutor():单线程化的线程池。它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。可以用于需要保证顺序执行的场景,并且只有一个线程在执行

使用ThreadPoolExecutor自定义的线程池

阿里巴巴Java开发手册,明确指出不允许使用上述Executors静态工厂构建线程池 原因如下:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险,同时Executors返回的线程池对象的弊端如下:

  1. FixedThreadPool 和 SingleThreadPool:允许的请求队列(底层实现是LinkedBlockingQueue)长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM

  2. CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

ThreadPoolExecutor创建

避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池。在创建的同时,给BlockQueue指定容量就可以了。

private static ExecutorService executor = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(10));

或者是使用开源类库:开源类库,如apache和guava等。

ThreadPoolExecutor的执行流程

  1. 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务。

  2. 线程数量达到了corePools,则将任务移入队列等待。

  3. 队列已满,新建线程(非核心线程)执行任务。

  4. 队列已满,总线程数又达到了maximumPoolSize,就会由(RejectedExecutionHandler)抛出异常(拒绝策略)

  5. 新建线程->达到核心数->加入队列->新建线程(非核心)->达到最大数->触发拒绝策略

ThreadPoolExecutor参数说明

  1. corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。

  2. maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;当阻塞队列是无界队列,则maximumPoolSize不起作用,因为无法提交至核心线程池的线程会一直持续地放入workQueue(工作队列)中。

  3. keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0。

  4. allowCoreThreadTimeout:默认情况下超过keepAliveTime的时候,核心线程不会退出,可通过将该参数设置为true,让核心线程也退出。

  5. unit:可以指定keepAliveTime的时间单位。

  6. workQueue

    • ArrayBlockingQueue 是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。需要指定队列大小。
  • LinkedBlockingQueue若指定大小则和ArrayBlockingQueue类似,若不指定大小则默认能存储Integer.MAX_VALUE个任务,相当于无界队列,此时maximumPoolSize值其实是无意义的。此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列

  • SynchronousQueue同步阻塞队列,当有任务添加进来后,必须有线程从队列中取出,当前线程才会被释放,newCachedThreadPool就使用这种队列。

  • PriorityBlockingQueue 一个具有优先级的无限阻塞队列。

  • RejectedExecutionHandler:线程数和队列都满的情况下,线程池会执行的拒绝策略,有四个(也可以使用自定义的策略)。

    • AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满,线程池默认策略。
  • DiscardPolicy:不执行新任务,也不抛出异常,基本上为静默模式。

  • DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行。

  • CallerRunPolicy:拒绝新任务进入,如果该线程池还没被关闭,那么这个新的任务在执行线程中被调用。

  • Executors和ThreadPoolExecutor创建线程的区别

如何向线程池中提交任务

可以通过execute()或submit()两个方法向线程池提交任务。

  • execute()方法没有返回值,所以无法判断任务知否被线程池执行成功。

  • submit()方法返回一个future,那么我们可以通过这个future来判断任务是否执行成功,通过future的get方法来获取返回值。

如何关闭线程池

可以通过shutdown()或shutdownNow()方法来关闭线程池。

  • shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。

  • shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。

初始化线程池时线程数的选择

  • 如果任务是IO密集型,一般线程数需要设置2倍CPU数以上,以此来尽量利用CPU资源。

  • 如果任务是CPU密集型,一般线程数量只需要设置CPU数加1即可,更多的线程数也只能增加上下文切换,不能增加CPU利用率。

上述只是一个基本思想,如果真的需要精确的控制,还是需要上线以后观察线程池中线程数量跟队列的情况来定。

线程优先级


Linux中,使用nice value(以下成为nice值)来设定一个进程的优先级,系统任务调度器根据nice值合理安排调度。

nice的取值范围为-20到19。 通常情况下,nice的默认值为0。视具体操作系统而定。 nice的值越大,进程的优先级就越低,获得CPU调用的机会越少,nice值越小,进程的优先级则越高,获得CPU调用的机会越多。 一个nice值为-20的进程优先级最高,nice值为19的进程优先级最低。 父进程fork出来的子进程nice值与父进程相同。父进程renice,子进程nice值不会随之改变。

由于Android基于Linux Kernel,在Android中也存在nice值。但是一般情况下我们无法控制,原因如下:

Android系统并不像其他Linux发行版那样便捷地使用nice命令操作。 renice需要root权限,一般应用无法实现。

Android中的线程优先级别目前规定了如下,了解了进程优先级与nice值的关系,那么线程优先级与值之间的关系也就更加容易理解。

  • THREAD_PRIORITY_DEFAULT,默认的线程优先级,值为0。

  • THREAD_PRIORITY_LOWEST,最低的线程级别,值为19。

  • THREAD_PRIORITY_BACKGROUND 后台线程建议设置这个优先级,值为10。

  • THREAD_PRIORITY_FOREGROUND 用户正在交互的UI线程,代码中无法设置该优先级,系统会按照情况调整到该优先级,值为-2。

  • THREAD_PRIORITY_DISPLAY 也是与UI交互相关的优先级界别,但是要比THREAD_PRIORITY_FOREGROUND优先,代码中无法设置,由系统按照情况调整,值为-4。

  • THREAD_PRIORITY_URGENT_DISPLAY 显示线程的最高级别,用来处理绘制画面和检索输入事件,代码中无法设置成该优先级。值为-8。 THREAD_PRIORITY_AUDIO 声音线程的标准级别,代码中无法设置为该优先级,值为 -16。

  • THREAD_PRIORITY_URGENT_AUDIO 声音线程的最高级别,优先程度较THREAD_PRIORITY_AUDIO要高。代码中无法设置为该优先级。值为-19。

  • THREAD_PRIORITY_MORE_FAVORABLE 相对THREAD_PRIORITY_DEFAULT稍微优先,值为-1。

  • THREAD_PRIORITY_LESS_FAVORABLE 相对THREAD_PRIORITY_DEFAULT稍微落后一些,值为1。

使用Android API为线程设置优先级也很简单,只需要在线程执行时调用android.os.Process.setThreadPriority方法即可。这种在线程运行时进行修改优先级,效果类似renice。

Android应用程序包含线程


最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

715268766655)]

[外链图片转存中…(img-k1Z9S24V-1715268766655)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值