线程池的使用

来源:互联网 发布:做篮球数据分析师就业 编辑:程序博客网 时间:2024/05/16 09:32

Android开发中离不开线程的使用,直接new Thread是最常见的方式。但是直接new Thread在线程数量比较少的时候是没问题,但是项目中需要使用到大量的线程或者在某些情况下,需要更好的方式,这就是线程池。
我们先看看直接使用线程会存在的问题:
1.每个任务都要开启新的线程、任务结束后销毁线程
2.开启线程,销毁线程都是不小的开销
3.创建、执行和销毁线程非常耗cpu和内存
4.频繁创建销毁线程导致GC频繁执行会产生内存抖动,导致界面卡顿
线程池的优势
1.复用线程,节省创建销毁耗费的时间
2.复用线程,节省创建销毁耗费的系统内存及CPU资源
3.可以控制线程的最大并发数、线程的定时任务、单线程的顺序执行
线程池的基本概念
1.corePoolSize(核心线程数):默认情况下核心线程在线程池开启后一旦被启动就会一直驻留在内存中,不会销毁的线程。核心线程即使在空闲时也不会被销毁,时刻处于待命状态,当有新的线程需要被调用,空闲的核心线程就能够立即投入工作。核心线程数越多,一直占用的资源就越多,但同时能够立刻投入工作的线程也越多。所以核心线程是用空间换时间。当然对于核心线程来说也可以设置他允许被销毁,这个时候需要将线程池的allowCoreThreadTimeOut属性设置为true,这样当核心线程空闲时间超过一定时长后(该时长属性为keepAliveTime),核心线程也会被销毁掉。
2.maximumPoolSize(线程池所能同时运行的最大线程数):除长驻的核心线程,线程池可以创建一些临时的非核心线程,非核心线程数+核心线程数<=线程池所能容纳的最大线程数。这些非核心线程的特点是,当所有核心线程都已经满负荷工作时,线程池就会开始创建非核心线程,当非核心线程完成自己的工作后,非核心线程并不会马上被销毁,而是驻留在内存一段时间(这个时间即为属性keepAliveTime),如果在销毁前有新的线程调用,则空闲的非核心线程会被复用,超时则销毁空闲非核心线程。当放入线程池的线程数多于线程池所能同时运行的最大线程数时,后续的线程会被阻塞,直到线程池中有线程空闲出来,被阻塞的线程才会运行。
3.keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。如果线程池的allowCoreThreadTimeOut属性设置为true,这个时长同样会作用于核心线程。
4.workQueue(线程池中的任务队列):线程池中会维护一个队列,以先进先出的模式实现线程的顺序执行。
自己配置线程池难度比较高,可以直接几种常用的线程池。
常见线程池
1.FixedThreadPool
调用方法

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(threadSize);fixedThreadPool.execute(new Runnable() {    @Override    public void run() {}});

线程池特点
一种线程数量固定的线程池,所有线程都是核心线程。超出线程池大小的线程会等待直到有线程空闲出来,等待线程会按加入线程池的顺序依次执行。由于线程不会被回收,所以这种线程池可以更加快速的响应外界的请求。
适用情况

  • 需要快速响应外界请求的情况。
  • 有多线程下载需求的应用可以使用这种线程池,控制同时下载的并发量保证应用能够流畅运行。

2.CachedThreadPool
调用方法

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();cachedThreadPool.execute(new Runnable() {    @Override    public void run() {    }});

线程池特点
线程池中只有非核心线程,且没有最大线程数上限,默认支持非核心线程超时机制(超时时间60s),运行新线程时,优先复用空闲线程,没有空闲线程的情况下,立刻创建新线程。因为没有核心线程,所以在没有任何线程执行时,线程池几乎不占用任何资源。
适用情况

  • 适合与执行大量耗时较少的任务
  • 如果应用中仅是偶尔有线程需求,也可以选择该线程池使用

3.ScheduledThreadPool
调用方法

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);//从线程池中启动一个线程,此线程3秒钟后启动scheduledThreadPool.schedule(new Runnable() {    @Override    public void run() {    }}, 3, TimeUnit.SECONDS);//从线程池中启动一个线程,此线程1秒钟后启动,并每3秒重复执行一次scheduledThreadPool.scheduleAtFixedRate(new Runnable() {    @Override    public void run() {    }}, 1, 3, TimeUnit.SECONDS)

线程池特点
核心线程数量固定,非核心线程数量没有限制,并且非核心线程闲置时立刻销毁(但通过测试,没有启动过非核心线程,线程池同时启动的最多线程数就是设定的coreThreadSize)。可以设置执行任务的周期或用于执行定时任务。
scheduleAtFixedRate()方法会周期性的启动线程,但是这个周期性的启动不会同时启动多条线程,所以示例代码中的调用,假如run()中的执行时间超过3秒,下次启动并不会在3秒后启动,而是在上一次执行完成后马上执行,所以如果要严格保证执行的周期性,run()中不要执行代码时间不要超过重复周期。
适用情况

  • 轮询操作时可以使用这个线程

4.SingleThreadExecutor
调用方法

ExecutorService singleThreadExcutor = Executors.newSingleThreadExecutor();singleThreadExcutor.execute(new Runnable() {    @Override    public void run() {}});

线程池特点
线程池中只有一个核心线程,不允许存在非核心线程,他确保所有的任务都在同一个线程中按顺序执行。实际上这个线程池和newFixedThreadPool(1)是一样的。
适用情况

  • 应用中需要有严格执行顺序的多线程操作

以上常用的由api提供的配置好的线程池,基本可以满足一般性的需求。实际上这几种线程都是根据不同的ThreadPoolExecutor参数配置出来的线程池。
FixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {    return new ThreadPoolExecutor(nThreads, nThreads,                                  0L, TimeUnit.MILLISECONDS,                                  new LinkedBlockingQueue<Runnable>());}

CachedThreadPool

public static ExecutorService newCachedThreadPool() {    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                  60L, TimeUnit.SECONDS,                                  new SynchronousQueue<Runnable>());}

ScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {    return new ScheduledThreadPoolExecutor(corePoolSize);}public ScheduledThreadPoolExecutor(int corePoolSize) {    super(corePoolSize, Integer.MAX_VALUE,          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,          new DelayedWorkQueue());}

SingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {    return new FinalizableDelegatedExecutorService        (new ThreadPoolExecutor(1, 1,                                0L, TimeUnit.MILLISECONDS,                                new LinkedBlockingQueue<Runnable>()));}

ThreadPoolExecutor()构造方法声明

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)

从这些工场方法对比ThreadPoolExecutor的构造方法我们就能看出不同种类的线程池就是对核心线程,线程池大小,线程超时时间和等待队列的不同组合配置。其中比较不好理解的是等待队列。

等待队列维护着等待执行的Runnable对象

当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务

常用的workQueue类型:

SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

如果不满足于api提供的线程池,可以自己配置线程池的核心线程数,线程池大小,线程超时时间和与之对应的等待队列,如果等待队列的类型选错了,那么线程池的执行效果就会跟预期出现偏差,这点要非常注意。

原创粉丝点击