Java - 线程池

xiaoxiao2021-02-28  70

参考: Android的App中线程池的使用,具体使用多少个线程池?

1)UIL的线程池处理非常简单粗暴,没有根据CPU数量来选择,也没有根据网络状况的变化进行调整;

2)Picasso的线程池会根据网络状况的变化进行调整,在Wifi下线程数为4,而4G下线程数为3, 3G下为2, 2G下为1,默认状况为3;

3)Glide加载缓存未命中的线程池会根据根据CPU的数量和Java虚拟机中可用的处理器数量来选择合适的线程数,但是最多不超过4;而加载缓存命中的图片的线程池默认大小为1.

聪明如你,到这里一定可以想到,将Picasso和Glide的特性结合起来是一个不错的选择,考虑到它们的线程池都可以自己配置,所以只要写一个自定义的线程池即可。

参考:

android线程池ThreadPoolExecutor的理解

Android开发——Android中常见的4种线程池(保证你能看懂并理解)

线程池

线程池顾名思义就是存放线程的一个容器的意思,容纳的就是Thread or Runable, 注意:每一个线程都是需要CPU分配资源去执行的。

如果由于总是new Thread()开启一个线程,那么就会大量的消耗CPU的资源,导致Android运行变慢,甚至OOM(out of memory) ,

因而java就出现了一个ThreadPoolExecutor来管理这些线程。控制最多的线程数maximumPoolSize, 核心线程数corePoolSize,来管理我们需要开启的线程数。

目的:减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

所以:我们就可以根据手机的CPU核数来控制App可以开的最大线程数。保证程序的合理运行

使用线程池可以给我们带来很多好处, 首先通过线程池中线程的重用,减少创建和销毁线程的性能开销。 其次,能控制线程池中的并发数,否则会因为大量的线程争夺CPU资源造成阻塞。 最后,线程池能够对线程进行管理,比如使用ScheduledThreadPool来设置延迟N秒后执行任务,并且每隔M秒循环执行一次。

创建线程池对象

线程池几个参数的理解:

比如去火车站买票, 有10个售票窗口, 但只有5个窗口对外开放. 那么对外开放的5个窗口称为核心线程数corePoolSize,

最大线程数maximumPoolSize是10个窗口.

如果5个窗口都被占用, 那么后来的人就必须在后面排队, 但后来售票厅人越来越多, 已经人满为患, 就类似于线程队列new LinkedBlockingQueue()已满.

这时候火车站站长下令, 把剩下的5个窗口也打开, 也就是目前已经有10个窗口同时运行. 后来又来了一批人, 10个窗口也处理不过来了,

而且售票厅人已经满了, 这时候站长就下令封锁入口,不允许其他人再进来, 这就是线程异常处理策略.

线程存活时间keepAliveTime指的是, 允许售票员休息的最长时间, 以此限制售票员偷懒的行时间。休息一下在处理。

单列的模式创建线程池ThreadPoolExecutor

Executor作为一个接口,它的具体实现就是ThreadPoolExecutor。 Android中的线程池都是直接或间接通过配置ThreadPoolExecutor来实现不同特性的线程池。

ThreadPoolExecutor线程池用于管理线程任务队列、若干个线程。

ThreadPoolExecutor构造函数:

ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue workQueue, RejectedExecutionHandler handler) ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

corePoolSize: 线程池维护线程的最少数量 maximumPoolSize:线程池维护线程的最大数量 keepAliveTime: 线程池维护线程所允许的空闲时间 unit: 线程池维护线程所允许的空闲时间的单位 workQueue: 线程池所使用的缓冲队列 threadFactory:线程池用于创建线程 handler: 线程池对拒绝任务的处理策略

ThreadPoolExecutor的各个参数所代表的特性注释中已经写的很清楚了,那么ThreadPoolExecutor执行任务时的心路历程是什么样的呢?(以下用currentSize表示线程池中当前线程数量)

(1)当currentSize < corePoolSize时,没什么好说的,直接启动一个核心线程并执行任务。 (2)当currentSize >= corePoolSize、并且workQueue未满时,添加进来的任务会被安排到workQueue中等待执行。 (3)当workQueue已满,但是currentSize < maximumPoolSize时,会立即开启一个非核心线程来执行任务。 (4)当currentSize >= corePoolSize、workQueue已满、并且currentSize >maximumPoolSize时,调用handler默认抛出RejectExecutionExpection异常。

常见几种BlockingQueue(阻塞队列)实现

下面两幅图演示了BlockingQueue的两个常见阻塞场景:

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

如上图所示:当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。

这也是我们在多线程环境下,为什么需要BlockingQueue的原因。作为BlockingQueue的使用者,我们再也不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了。

ArrayBlockingQueue : 有界的数组队列 这个表示一个规定了大小的BlockingQueue,ArrayBlockingQueue的构造函数接受一个int类型的数据,该数据表示BlockingQueue的大小,存储在ArrayBlockingQueue中的元素按照FIFO(先进先出)的方式来进行存取。

LinkedBlockingQueue : 可支持有界/无界的队列,使用链表实现. 这个表示一个大小不确定的BlockingQueue,在LinkedBlockingQueue的构造方法中可以传一个int类型的数据,这样创建出来的LinkedBlockingQueue是有大小的,也可以不传,不传的话,LinkedBlockingQueue的大小就为Integer.MAX_VALUE

PriorityBlockingQueue : 优先阻塞队列,可以针对任务排序 这个队列和LinkedBlockingQueue类似,不同的是PriorityBlockingQueue中的元素不是按照FIFO来排序的,而是按照元素的Comparator来决定存取顺序的(这个功能也反映了存入PriorityBlockingQueue中的数据必须实现了Comparator接口)。

SynchronousQueue : 队列长度为1的队列,和Array有点区别就是:client thread提交到block queue会是一个阻塞过程,直到有一个worker thread连接上来poll task。 这个是同步Queue,属于线程安全的BlockingQueue的一种,在SynchronousQueue中,生产者线程的插入操作必须要等待消费者线程的移除操作,Synchronous内部没有数据缓存空间,因此我们无法对SynchronousQueue进行读取或者遍历其中的数据,元素只有在你试图取走的时候才有可能存在。我们可以理解为生产者和消费者互相等待,等到对方之后然后再一起离开。

BlockingQueue成员详细介绍

ArrayBlockingQueue

基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。   ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。而在创建ArrayBlockingQueue时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。

LinkedBlockingQueue

基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。 作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。

ArrayBlockingQueue和LinkedBlockingQueue是两个最普通也是最常用的阻塞队列,一般情况下,在处理多线程间的生产者消费者问题,使用这两个类足以。

DelayQueue

DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。 使用场景:   DelayQueue使用场景较少,但都相当巧妙,常见的例子比如使用一个DelayQueue来管理一个超时未响应的连接队列。

PriorityBlockingQueue

基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。

SynchronousQueue

一种无缓冲的等待队列,类似于无中介的直接交易,有点像原始社会中的生产者和消费者,生产者拿着产品去集市销售给产品的最终消费者,而消费者必须亲自去集市找到所要商品的直接生产者,如果一方没有找到合适的目标,那么对不起,大家都在集市等待。相对于有缓冲的BlockingQueue来说,少了一个中间经销商的环节(缓冲区),如果有经销商,生产者直接把产品批发给经销商,而无需在意经销商最终会将这些产品卖给那些消费者,由于经销商可以库存一部分商品,因此相对于直接交易模式,总体来说采用中间经销商的模式会吞吐量高一些(可以批量买卖);但另一方面,又因为经销商的引入,使得产品从生产者到消费者中间增加了额外的交易环节,单个产品的及时响应性能可能会降低。   声明一个SynchronousQueue有两种不同的方式,它们之间有着不太一样的行为。公平模式和非公平模式的区别:   如果采用公平模式:SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体系整体的公平策略;   但如果是非公平模式(SynchronousQueue默认):SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。

小结   BlockingQueue不光实现了一个完整队列所具有的基本功能,同时在多线程环境下,他还自动管理了多线间的自动等待于唤醒功能,从而使得程序员可以忽略这些细节,关注更高级的功能。

参考: Android线程管理之ThreadPoolExecutor自定义线程池 Android线程管理之ExecutorService线程池 Android线程管理之Thread使用总结 Android线程管理之AsyncTask异步任务 Android线程管理之ThreadLocal理解及应用场景

Java多线程-工具篇-BlockingQueue

public static class ThreadPool { public static ThreadPoolExecutor executor = null; private int corePoolSize; private int maximumPoolSize; private long keepAliveTime = 0; // 限制线程的的最大存活时间 public ThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime) { super(); this.corePoolSize = corePoolSize; //核心线程数 this.maximumPoolSize = maximumPoolSize; //最大线程 ,当核心线程用完时。决定是否开启最大线程 this.keepAliveTime = keepAliveTime; //线程排队时间, } /** * 线程池:就是把一堆线程放在一起来管理。 1.通过一定的管理机制。来处理线程额执行顺序 2.管理最多可以同时执行的线程数。 * 3.其他线程通过队列的形式,也就是排队的形式来管理线程的并发数。 * * @param runnable */ public void execute(Runnable runnable) { if (runnable == null) { return; } if (executor == null) { executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS,// 时间单位 new LinkedBlockingQueue<Runnable>(),// 线程队列 Executors.defaultThreadFactory(),//线程工厂 new AbortPolicy()); } // 给线程池里面添加一个线程 executor.execute(runnable); }

创建线程池管理者 ThreadManager

通过给线程加锁机制。来保证线程安全,以及当前程序当中只有一个x线程池ThreadPool

/** * 线程管理者。 * * @author H_lang * */ public class ThreadManager { private static ThreadPool threadPool; // 单列的线程池对象。 /** * 单列,线程安全 * 获取一个线程池对象 * @return */ public static ThreadPool getThreadPool() { if (threadPool == null) { //枷锁 synchronized (ThreadManager.class) { if (threadPool == null) { //核心线程数,等于处理器个数乘2 int corePoolSize = Runtime.getRuntime().availableProcessors()*2; int maximumPoolSize = 10; long keepAliveTime = 0L; threadPool = new ThreadPool(corePoolSize, maximumPoolSize, keepAliveTime); } } } return threadPool; } public static class ThreadPool { public static ThreadPoolExecutor executor = null; private int corePoolSize; private int maximumPoolSize; private long keepAliveTime = 0; // 限制线程的的最大存活时间 public ThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime) { super(); this.corePoolSize = corePoolSize; //核心线程数 this.maximumPoolSize = maximumPoolSize; //最大线程 ,当核心线程用完时。决定是否开启最大线程 this.keepAliveTime = keepAliveTime; //线程排队时间, } /** * 线程池:就是把一堆线程放在一起来管理。 1.通过一定的管理机制。来处理线程额执行顺序 2.管理最多可以同时执行的线程数。 * 3.其他线程通过队列的形式,也就是排队的形式来管理线程的并发数。 * * @param runnable */ public void execute(Runnable runnable) { if (runnable == null) { return; } if (executor == null) { executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS,// 时间单位 new LinkedBlockingQueue<Runnable>(),// 线程队列 Executors.defaultThreadFactory(),//线程工厂 new AbortPolicy()); } // 给线程池里面添加一个线程 executor.execute(runnable); } } }

Executors(工厂类)提供四种线程池

Java通过Executors提供四种线程池,分别为: newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。 newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

public interface Executor {...} public interface ExecutorService extends Executor {...} public abstract class AbstractExecutorService implements ExecutorService {...} public class ThreadPoolExecutor extends AbstractExecutorService {...} public class Executors {...} ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

(1) newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 创建:

public static ExecutorService newCachedThreadPool(int nThreads){ return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit. SECONDS, new SynchronousQueue<Runnable>()); } //使用 Executors.newCachedThreadPool().execute(r);

CachedThreadPool只有非核心线程,最大线程数非常大,所有线程都活动时,会为新任务创建新线程,否则利用空闲线程(60s空闲时间,过了就会被回收,所以线程池中有0个线程的可能)处理任务。

任务队列SynchronousQueue相当于一个空集合,导致任何任务都会被立即执行。

比较适合执行大量的耗时较少的任务。

示例代码如下:

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /*创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 * 线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。 * * 线程池里有很多线程需要同时执行,老的可用线程将被新的任务触发重新执行, * 如果线程超过60秒内没执行,那么将被终止并从池中删除*/ class ThreadPoolExecutorTest1 { public static void main(String[] args) { ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int index = i; try { Thread.sleep(index * 1000); } catch (InterruptedException e) { e.printStackTrace(); } cachedThreadPool.execute(new Runnable() { public void run() { System.out.println(index); } }); } } }

(2) newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 创建:

public static ExecutorService newFixThreadPool(int nThreads){ return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } //使用 Executors.newFixedThreadPool(5).execute(r);

从配置参数来看,FixThreadPool只有核心线程,并且数量固定的,也不会被回收,所有线程都活动时,因为队列没有限制大小,新任务会等待执行。 由于线程不会回收,FixThreadPool会更快地响应外界请求。

示例代码如下:

import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created by Administrator on 2017/5/6. */ /*创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 * * 因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。 定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()*/ public class ThreadPoolExecutorTest2 { public static void main(String[] args) { ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { final int index = i; fixedThreadPool.execute(new Runnable() { public void run() { try { System.out.println(index); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); } } }

(3) newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。 创建:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){ return new ScheduledThreadPoolExecutor(corePoolSize); } public ScheduledThreadPoolExecutor(int corePoolSize){ super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedQueue ()); } //使用,延迟1秒执行,每隔2秒执行一次Runnable r Executors. newScheduledThreadPool (5).scheduleAtFixedRate(r, 1000, 2000, TimeUnit.MILLISECONDS);

核心线程数固定,非核心线程(闲着没活干会被立即回收)数没有限制。

从上面代码也可以看出,ScheduledThreadPool主要用于执行定时任务以及有固定周期的重复任务

延迟执行示例代码如下:

import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * Created by Administrator on 2017/5/6. */ /*创建一个定长线程池,支持定时及周期性任务执行。 * * 表示延迟1秒后每3秒执行一次。*/ public class ThreadPoolExecutorTest3 { public static void main(String[] args) { ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); scheduledThreadPool.scheduleAtFixedRate(new Runnable() { public void run() { System.out.println("delay 1 seconds, and excute every 3 seconds"); } }, 1, 3, TimeUnit.SECONDS); } }

(4) newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 创建:

public static ExecutorService newSingleThreadPool (int nThreads){ return new FinalizableDelegatedExecutorService ( new ThreadPoolExecutor (1, 1, 0, TimeUnit. MILLISECONDS, new LinkedBlockingQueue<Runnable>()) ); } //使用 Executors.newSingleThreadPool ().execute(r);

从配置参数可以看出,SingleThreadPool只有一个核心线程,确保所有任务都在同一线程中按顺序完成。因此不需要处理线程同步的问题。

示例代码如下:

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created by Administrator on 2017/5/6. */ /*创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 * * 结果依次输出,相当于顺序执行各个任务。*/ public class ThreadPoolExecutorTest4 { public static void main(String[] args) { ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int index = i; singleThreadExecutor.execute(new Runnable() { public void run() { try { System.out.println(index); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); } } }

推荐:

深入理解Java之线程池 Java线程池 ExecutorService 参考:

Java四种线程池的使用

Android线程优先级

方法一. android.os.Process.setThreadPriority (int priority) 或 android.os.Process.setThreadPriority (int tid, int priority) priority:【-20, 19】,高优先级 -> 低优先级。

方法二. java.lang.Thread.setPriority (int priority) priority:【1, 10】,低优先级 -> 高优先级。

测试表明,android自己的API(第1种方法)设置的优先级,对线程调度影响显著

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //设置线程优先级为后台,这样当多个线程并发后很多无关紧要的线程分配的CPU时间将会减少,有利于主线程的处理,有以下几种:

int THREAD_PRIORITY_AUDIO //标准音乐播放使用的线程优先级

int THREAD_PRIORITY_BACKGROUND //标准后台程序

int THREAD_PRIORITY_DEFAULT // 默认应用的优先级

int THREAD_PRIORITY_DISPLAY //标准显示系统优先级,主要是改善UI的刷新

int THREAD_PRIORITY_FOREGROUND //标准前台线程优先级

int THREAD_PRIORITY_LESS_FAVORABLE //低于favorable

int THREAD_PRIORITY_LOWEST //有效的线程最低的优先级

int THREAD_PRIORITY_MORE_FAVORABLE //高于favorable

int THREAD_PRIORITY_URGENT_AUDIO //标准较重要音频播放优先级

int THREAD_PRIORITY_URGENT_DISPLAY //标准较重要显示优先级,对于输入事件同样适用。

okhttp的线程:

asynctask的线程:

转载请注明原文地址: https://www.6miu.com/read-45043.html

最新回复(0)