一、概述
在Android开发中,如果我们用到线程,鼓励采用线程池的方式,不要显示的创建一个线程。这也是《阿里巴巴Android开发手册》中要求的,理由如下:
使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建了大量同类线程而导致消耗完内存或者“过度切换”的问题。另外创建匿名线程不便于后续的资源使用分析,对性能分析等会造成困扰。
二、线程池基本概念
我们先学习一下,线程池的一些相关类和使用,请看这篇文章 java线程池ThreadPoolExecutor类使用详解
上面这篇文章讲的很全面,不过当LinkedBlockingQueue和PriorityBlockingQueue使用的时候上需要说明一下,如果在使用LinkedBlockingQueue和PriorityBlockingQueue的时候,corePoolSize是0,maxPoolSize不为0的话,线程池也会创建线程执行任务的,不过只会创建一个。
大家可以用下面的代码,对线程池学习测试
package com.lzy.learnpro;
import java.util.PriorityQueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Author zhongyili
* @Date 2020/2/19
*/
public class JavaCodeMain {
static final int CORE_POOL_SIZE = 0;
static final int MAX_POOL_SIZE = 5;
public static void main(String[] args) {
//生成一个队列
BlockingQueue<Runnable> queue = JavaCodeMain.createQueue(PriorityBlockingQueue.class);
ThreadPoolExecutor tpExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
1000L,
TimeUnit.MILLISECONDS,
queue,
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 20; i++) {
tpExecutor.execute(new WorkRunnable(String.valueOf(i + 1)));
}
}
/**
* 这个是一个泛型方法,而且规定了泛型参数的上界
* 采用了简单工厂设计模式
* @param queueClass
* @param <T> 队列类型
* @param <E> 队列中存的类型
* @return
*/
private static <T extends BlockingQueue<E>, E> BlockingQueue<E> createQueue(Class<T> queueClass) {
if (SynchronousQueue.class.isAssignableFrom(queueClass)) {
return new SynchronousQueue<>();
} else if (ArrayBlockingQueue.class.isAssignableFrom(queueClass)) {
return new ArrayBlockingQueue<>(10);
} else if (LinkedBlockingQueue.class.isAssignableFrom(queueClass)) {
return new LinkedBlockingQueue<>();
} else if (PriorityBlockingQueue.class.isAssignableFrom(queueClass)) {
return new PriorityBlockingQueue<>();
}
return null;
}
private static class WorkRunnable implements Runnable, Comparable<WorkRunnable> {
private static int CREATION_ORDER = 0;
private String runName;
private Priority priority;
private int creationOrder;
WorkRunnable(String name) {
runName = name;
priority = Priority.NORMAL;
creationOrder = CREATION_ORDER++;
}
WorkRunnable(String name, Priority priority) {
this(name);
this.priority = priority;
}
@Override
public void run() {
synchronized (this) {
System.out.printf("%s and %s", runName, Thread.currentThread().getName());
System.out.println();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public int compareTo(WorkRunnable o) {
int result = priority.ordinal() - o.priority.ordinal();
//如果优先级一样,根据创建顺序来排序
if (result == 0) result = creationOrder - o.creationOrder;
return result;
}
}
private enum Priority {
IMMEDIATE,
HIGH,
NORMAL,
LOW,
}
}
三、补充
1、线程池这块还有一个知识点是Future和Callable。
我们思考一个问题,我们A启动一个线程去网络上加载数据,一般会设置一个Callback,请求完数据后通过Callback回调把数据传回到A中,这种方式属于A被动通知。那有没有一种方式是让A能去主动获取网络请求回来的数据呢?答案是有的,就是使用Future和Callable来实现,其实实现很简单,看看ThreadPoolExecutor的源码就知道了,使用Future还可以和请求过程交互。
2、在Android中AsyncTask封装了ThreadPoolExecutor和Handler,可以去学习一下,也比较简单。AsyncTask内部实现了一个串行的线程池,而且这个线程池是属于类的,也就是说即便我们new 两个AsyncTask分别执行各自的工作,也是串行进行的。但是AsynTask提供了接口,能实现非串行,看如下的方法
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params)
传入AsyncTask.THREAD_POOL_EXECUTOR就可以了。
AsyncTask还有提供了一个静态方法,能让我们在线程池中串行执行工作
public static void execute(Runnable runnable) {
sDefaultExecutor.execute(runnable);
}
四、线程池原理
我们分析一下线程池执行的入口:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
workerCountof(c) < corePoolSize表示只要线程数量小于核心线程数量时,就会创建新线程执行任务,创建新线程是在addWorker里面做的。
addWorker里面,其实是创建了一个Worker,Woker实现了Runnable接口,Worker封装了外部传进来任务firstTask(Runable command),并且通过ThreadFactory实例化了一个线程Thread,newThread方法传的Runnable参数就是这个Worker;在addWorker中启动了这个线程,然后这个线程就可以执行Worker的run方法,run方法调用ThreadPoolExecutor的runWorker方法,参数是Worker,Worker是ThreaPoolExecutor的内部类;runWorker里面有个循环,如果Worker的firstTask不为空的话就执行firstTask,如果firstTask被执行完了,就从队列中取任务来执行,这个取任务就有讲究了,也是重点。
取任务是在getTask方法中执行的,其实就是从Queue中取任务,只是取的方式不一样,如果workerCountof(c)线程数量大于核心线程数量corePoolSize,采用超时的方式去取,如果取不到,这个线程就执行结束了。如果线程数量小于等于核心线程数量,队列就阻塞起来,直到取到消息。
需要说明的一点时,workerCountof(c)是不会统计超时阻塞的线程的,具体细节,目前没有细研究。
总结一下:
(1)只要核心线程数量没创建的完全,执行任务时,都会创建新线程执行,直到核心线程都创建完成。如果线程数量大于核心线程时,多余核心线程数量的线程在超时后都会释放退出。
(2)线程是怎么重用的?其实很简单,就是线程不断从队列中取任务,取不到就阻塞起来,取到了就继续执行,这样线程是不是就重用了呢。Android 主线程也是这种思想。
(3)我们再看看execute的源码,如果运行线程的数量大于等于了核心线程数量,那么任务就会放入队列中,不同队列不同处理方式,原因在源码中都能看到。
五、一篇不错的外文文章
Using ThreadPoolExecutor in Android
参考
java并发编程:Executor、Executors、ExecutorService
怎么根据Comparable方法中的compareTo方法的返回值的正负 判断升序 还是 降序?
Java 中 Comparable 和 Comparator 比较
这篇文章中推荐的更多文章中,每一篇都值得学习,特别是Iterator的文章
Thread 的源码和原理也得学习一下