Android(java)学习笔记267:Android线程池形态

时间:2024-03-26 21:37:56

1. 线程池简介

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

如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。
                一个线程池包括以下四个基本组成部分:
                1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
                2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
                3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
                4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。

线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目,看一个例子:
    假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的服务器程序不会为了创建50000个线程而在处理请求时浪费时间,把时间花在处理请求的过程中,而减少花费在创建线程的时间,从而提高效率。

2. 总而言之,线程池的优点

  • 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
  • 能有效控制线程池的最大并发数,避免大量的线程之间因相互抢占系统资源而导致的阻塞现象。
  • 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。

3. 代码示例如下,代码实现中并没有实现任务接口,而是把Runnable对象加入到线程池管理器(ThreadPool),然后剩下的事情就由线程池管理器(ThreadPool)来完成了:

 ThreadPoolDemo:

 package com.himi.threadpool;

 import java.util.LinkedList;
import java.util.List; /**
* 线程池类,线程管理器:1. 创建线程 2.行任务 3.销毁线程 4.获取线程基本信息
*/
public class ThreadPoolDemo {
// 线程池中默认线程的个数为5
private static int worker_num = 5;
// 工作线程
private WorkThread[] workThrads;
// 已完成任务
private static volatile int finished_task = 0;
// 任务队列,作为一个缓冲,List线程不安全
private List<Runnable> taskQueue = new LinkedList<Runnable>();
private static ThreadPoolDemo threadPool; // 创建具有默认线程个数的线程池
private ThreadPoolDemo() {
this(5);
} // 创建线程池,worker_num为线程池中工作线程的个数
private ThreadPoolDemo(int worker_num) {
ThreadPoolDemo.worker_num = worker_num;
workThrads = new WorkThread[worker_num];
for (int i = 0; i < worker_num; i++) {
workThrads[i] = new WorkThread();
workThrads[i].start();// 开启线程池中的线程
}
} // 单例模式,获得一个默认线程个数的线程池
public static ThreadPoolDemo getThreadPool() {
return getThreadPool(ThreadPoolDemo.worker_num);
} // 单例模式,获得一个指定线程个数的线程池,worker_num(>0)为线程池中工作线程的个数
// worker_num<=0创建默认的工作线程个数
public static ThreadPoolDemo getThreadPool(int worker_num1) {
if (worker_num1 <= 0)
worker_num1 = ThreadPoolDemo.worker_num;
if (threadPool == null)
threadPool = new ThreadPoolDemo(worker_num1);
return threadPool;
} // 执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器决定
public void execute(Runnable task) {
synchronized (taskQueue) {
taskQueue.add(task);
taskQueue.notify();
}
} // 批量执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器决定
public void execute(Runnable[] task) {
synchronized (taskQueue) {
for (Runnable t : task)
taskQueue.add(t);
taskQueue.notify();
}
} // 批量执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器决定
public void execute(List<Runnable> task) {
synchronized (taskQueue) {
for (Runnable t : task)
taskQueue.add(t);
taskQueue.notify();
}
} // 销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁
public void destroy() {
while (!taskQueue.isEmpty()) {// 如果还有任务没执行完成,就先睡会吧
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 工作线程停止工作,且置为null
for (int i = 0; i < worker_num; i++) {
workThrads[i].stopWorker();
workThrads[i] = null;
}
threadPool=null;
taskQueue.clear();// 清空任务队列
} // 返回工作线程的个数
public int getWorkThreadNumber() {
return worker_num;
} // 返回已完成任务的个数,这里的已完成是只出了任务队列的任务个数,可能该任务并没有实际执行完成
public int getFinishedTasknumber() {
return finished_task;
} // 返回任务队列的长度,即还没处理的任务个数
public int getWaitTasknumber() {
return taskQueue.size();
} // 覆盖toString方法,返回线程池信息:工作线程个数和已完成任务个数
@Override
public String toString() {
return "WorkThread number:" + worker_num + " finished task number:"
+ finished_task + " wait task number:" + getWaitTasknumber();
} /**
* 内部类,工作线程
*/
private class WorkThread extends Thread {
// 该工作线程是否有效,用于结束该工作线程
private boolean isRunning = true; /*
* 关键所在啊,如果任务队列不空,则取出任务执行,若任务队列空,则等待
*/
@Override
public void run() {
Runnable r = null;
while (isRunning) {// 注意,若线程无效则自然结束run方法,该线程就没用了
synchronized (taskQueue) {
while (isRunning && taskQueue.isEmpty()) {// 队列为空
try {
taskQueue.wait(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (!taskQueue.isEmpty())
r = taskQueue.remove(0);// 取出任务
}
if (r != null) {
r.run();// 执行任务
}
finished_task++;
r = null;
}
} // 停止工作,让该线程自然执行完run方法,自然结束
public void stopWorker() {
isRunning = false;
}
}
}

TestThreadPool:

 package com.himi.threadpool;

 //测试线程池
public class TestThreadPool {
public static void main(String[] args) {
// 创建3个线程的线程池
ThreadPoolDemo t = ThreadPoolDemo.getThreadPool(3);
t.execute(new Runnable[] { new Task(), new Task(), new Task() });
t.execute(new Runnable[] { new Task(), new Task(), new Task() }); System.out.println(t); t.destroy();// 所有线程都执行完成才destory System.out.println(t);
} // 任务类
static class Task implements Runnable {
private static volatile int i = 1; @Override
public void run() {// 执行任务
System.out.println("任务 " + (i++) + " 完成");
}
}
}

测试结果,如下:

Android(java)学习笔记267:Android线程池形态

 分析:由于并没有任务接口,传入的可以是自定义的任何任务,所以线程池并不能准确的判断该任务是否真正的已经完成(真正完成该任务是这个任务的run方法执行完毕),只能知道该任务已经出了任务队列,正在执行或者已经完成。

总而言之,上面线程池原理(执行流程)如下:

1)创建线程池(指定线程数量),开启所有线程,此时我们要知道这些开启了的线程都在等待执行任务队列中的任务(Runnable);

2)将所有待执行的任务全部放到任务队列之中;

3)线程池中线程不停取出任务队列中的任务,进而执行这些任务,一直到任务队列中任务全部执行完,注意这里代码是同步synchronized,我们可以放心并发问题。

4. Java库中提供的中的线程池简介:

Java中的线程池的概念来源于Java中的Executor,Executor是一个借口,真正的线程池实现为ThreadPoolExecutor。

至于Java中线程池简单使用可以参照笔记:Android(java)学习笔记74:Java线程池

Android(java)学习笔记267:Android线程池形态

ThreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池。

从线程池的功能特性上来说,Android线程池主要分为4类,这4类线程池可以通过Executor所提供的工厂方法来得到。

由于Android中的线程池都是直接或者间接通过配置ThreadPoolExecutor来实现的,因此我们这里先介绍ThreadPoolExecutor.

-->1. ThreadPoolExecutor

ThreadPoolExecutor是线程池真正的实现,它的构造方法提供了一系列参数来配置线程池。

下面会介绍ThreadPoolExecutor的构造方法中各个参数的含义,这些参数将会直接影响到线程池的功能特性,下面ThreadPoolExecutor的一个比较常用的构造方法:

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

corePoolSize: 

  线程池的核心线程数,默认情况下,核心线程数会在线程池中一直存活着,即使他们处于闲置状态。如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来的时候会有超时策略,这个时间间隔由keepAliveTime所指定,当

等待时间超过keepAliveTime所指定的时长后,核心线程就会被终止。

maximumPoolSize:

  线程池所能容纳的最大线程数,当活动线程数达到这个数值后,后续的新任务将会被阻塞。

keepAliveTime: 

  非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程。

unit: 

  用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit.MILLSECONDS(毫秒)、TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分钟)等。

workQueue: 

  线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。

threadFactory: 

  线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法:Thread newThread(Runnable r)

除了上面的这些主要参数外,ThreadPoolExecutor还有一个不常用的参数RejectedExecutionHandler  handler。

当线程池无法执行新任务时候,这可能是由于任务队列已满 或者是无法成功执行任务,这个时候ThreadPoolExecutor会调用handler的rejectedExecution方法来通知调用者,默认情况下rejectedExecutor方法会抛出一个RejectedExecutionException。

ThreadPoolExecutor为RejectedExecutionHandler  handler提供了几个可选值:

  • ThreadPoolExecutor.CallerRunsPolicy():当抛出RejectedExecutionException异常时,会调用rejectedExecution方法
  • ThreadPoolExecutor.AbortPolicy():抛出java.util.concurrent.RejectedExecutionException异常
  • ThreadPoolExecutor.DiscardOldestPolicy():抛弃旧的任务
  • ThreadPoolExecutor.DiscardPolicy():抛弃当前的任务

ThreadPoolExecutor执行任务时候大致遵循如下原则:

(1)如果线程池中的线程数量未达到核心线程的数量,那么会导致直接启动一个核心线程来执行任务。(线程池中线程数会维持核心线程数这个常态)

(2)如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。

(3)如果步骤(2)中无法将任务插入到任务队列之中,这往往是由于任务队列已经满了,这个时候如果线程数量未达到线程池规定的最大值,那么就会立刻启动一个非核心线程来执行任务。

(4)如果步骤(3)中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。

AsyncTask中关于ThreadPoolExecutor的参数配置有明显体现,下面是AsyncTask中的线程池的配置情况:

public abstract class AsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask"; private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 1; private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
}; private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(10); /**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
.........
.........
.........
.........
}

从上面的代码可以知道,AsyncTask对THREAD_POOL_EXECUTOR这个线程池进行了配置,配置后的线程池规格如下:

  • 核心线程数等于:
  • 线程池的最大线程数为:
  • 核心线程无超时机制,非核心线程在闲置时候的超时时间为1s
  • 任务队列的容量为:

5. 线程池的分类

  前面对ThreadPoolExecutor配置细节进行了详细的介绍,下面将会介绍Android中最常见的4类不同功能的线程池,而这4类线程池都直接或者间接通过配置ThreadPoolExecutor来实现自己的功能特性,这4类线程池分别是FixedThreadPool、CachedThreadPool、ScheduledThreadPool 以及SingleThreadExecutor。

(1)FixedThreadPool

通过Executors的newFixedThreadPool方法来创建的,它是一种线程数量固定的线程池。

    public static ExecutorService newFixedThreadPool(int nThreads)  //指定线程池中有多少个线程nThread

当线程处于空闲状态时候,它们并不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时候,新任务都会处于等待状态,直到有线程空闲出来。

由于FixedThreadPool只有核心线程并且这些核心线程不会被回收,可以发现FixedThreadPool中只有核心线程并且这些核心线程没有超时时限,另外任务队列也没有大小限制的。

(2)CachedThreadPool

通过Executors的ScheduledThreadPool方法来创建的,它是一种线程数量不定的线程池。

    public static ExecutorService newCachedThreadPool()

CachedThreadPool只有非核心线程,并且其最大线程数为Integer.MAX_VALUE(Integer.MAX_VALUE是一个很大的数,实际上就相当于最大线程数可以任意大).当线程池中的线程都处于活动状态时候,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务。线程池中的空闲线程都有超时机制,这个超时时长为60秒,超过60秒闲置线程就会被回收。

CachedThreadPoolFixedThreadPool不同的是,CachedThreadPool的任务队列其实相当于一个空集合,这就会导致任何任务都会立即被执行,因为在这种场景下SynchronousQueue是无法插入任务的。SynchronousQueue是一个非常特殊的队列,在很大情况下可以把它简单理解为一个无法存储元素的队列。

从CachedThreadPool的特性来看,这类线程池比较适合执行大量的耗时较少的任务。当整个线程池都处于闲置状态时候,线程池中的线程都会超时而被停止,这个时候CachedThreadPool之中实际上是没有任何线程的,它几乎是不占用任何系统资源的。

(3)ScheduledThreadPool

通过Executors的newCachedThreadPool方法来创建的,它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时候就会被立即回收。

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

ScheduledThreadPool这类线程池主要用来执行定时任务和具有固定周期的重复任务。

(4)SingleThreadExecutor

通过Executors的newSingleThreadPool方法来创建的,这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按照顺序执行的。

    public static ExecutorService newSingleThreadExecutor()//创建只有一个线程的线程池

SingleThreadExecutor的意义在于统一所有外界任务到一个线程之中,这使得在这些任务之间不需要处理线程同步的问题。

下面代码演示上面系统预置4种线程池使用:

Runnable command = new Runnable() {

            @Override
public void run() {
SystemClock.sleep(2000); }
}; ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
fixedThreadPool.execute(command); ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(command); ScheduledExecutorService scheduledThreadPool =Executors.newScheduledThreadPool(4);
//2000ms之后执行command
scheduledThreadPool.schedule(command, 2000, TimeUnit.MILLISECONDS);
//延迟10ms之后,每隔1000ms执行一次command
scheduledThreadPool.scheduleAtFixedRate(command, 10, 1000, TimeUnit.MILLISECONDS); ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(command);