1,总结
总结下线程和线程池相关的使用和知识
2,线程
在 Android 中,线程是用于执行并发任务的基本单位。Android 提供了多种方式来创建和管理线程,以满足不同的需求。
以下是在 Android 中使用线程的几种常见方式:
- Thread 类:可以直接使用 Java 的 Thread 类来创建和管理线程。你可以继承 Thread 类并重写 run() 方法来定义线程的执行逻辑。例如:
Thread thread = new Thread() {
@Override
public void run() {
// 线程的执行逻辑
}
};
thread.start(); // 启动线程
- Runnable 接口:可以实现 Runnable 接口,并将实现逻辑放在 run() 方法中。然后,通过 Thread 类来创建线程并传入 Runnable 对象。例如:
Runnable runnable = new Runnable() {
@Override
public void run() {
// 线程的执行逻辑
}
};
Thread thread = new Thread(runnable);
thread.start(); // 启动线程
- AsyncTask 类:AsyncTask 是一个方便的异步任务类,它可以简化在后台执行任务并在主线程更新 UI 的操作。AsyncTask 定义了 doInBackground() 方法来执行后台任务,和 onPostExecute() 方法来在任务完成后更新 UI。例如:
java复制
public class MyTask extends AsyncTask<Void, Void, String> {
@Override
protected String doInBackground(Void... voids) {
// 后台任务执行逻辑
return "result";
}
@Override
protected void onPostExecute(String result) {
// 更新 UI 的逻辑
}
}
MyTask task = new MyTask();
task.execute(); // 执行任务
- Handler 和 Looper:Handler 和 Looper 是 Android 中用于线程间通信和管理的重要组件。Handler 可以用来发送和处理消息,而 Looper 则负责循环处理消息队列。可以在主线程中创建一个 Handler,然后使用 post()、sendMessage() 等方法来向 Handler 发送消息,在 handleMessage() 方法中处理消息。例如:
java复制
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 处理消息的逻辑
}
};
handler.post(new Runnable() {
@Override
public void run() {
// 在主线程中执行的逻辑
}
});
需要注意的是,在 Android 中有一些特殊的线程,如主线程(UI 线程)和后台线程。主线程用于处理用户界面操作和更新,而后台线程用于执行耗时操作,以避免阻塞主线程和导致 ANR(应用无响应)的问题。
上述是 Android 中使用线程的几种常见方式,选择合适的方式取决于具体的需求和场景。同时,需要注意在多线程编程中处理线程同步、线程安全和避免内存泄漏等问题。
线程的状态
3,线程池
3.1 线程池的好处
1,线程管理:线程池可以有效的管理线程资源,避免线程创建销毁的开销,可以控制线程的数量,并且重用线程,提高资源利用率,减少系统资源消耗
2,提升性能:通过合理设置线程数量大小,可以根据系统处理能力协调任务执行,提高系统响应速度和性能表现
3,提高代码简洁性:使用线程池可以将任务的提交和执行分离,将并发细节交给线程池来管理,简洁代码
3.2 我们在安卓中常用有四种线程池
1,newFixedThreadPool
fixedThreadPool固定线程池,其中核心线程是固定的,如果所有线程都处于忙碌状态, 新的任务会在对列中等待,直到有线程空闲出来执行任务。
private void newFixedThreadPool() {
//在上述示例中,我们创建了一个固定大小为 5 的线程池,并提交了 10 个任务给线程池执行。
// 由于线程池的大小是固定的,只有 5 个线程会同时执行任务,剩余的任务会在队列中等待。
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
final int task = i;
executor.execute(new Runnable() {
@Override
public void run() {
// 执行任务的逻辑
Log.e("tag","===============Task " + task + " is running");
}
});
}
executor.shutdown();
}
2,cachedThreadPool
CachedThreadPool(缓存线程池): CachedThreadPool 是一个大小可变的线程池,它根据需要创建新的线程,如果有线程空闲一段时间没有被使用,则会被回收。
private void cachedThreadPool() {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int task = i;
executor.execute(new Runnable() {
@Override
public void run() {
// 执行任务的逻辑
Log.e("tag","===============Task " + task + " is running");
}
});
}
executor.shutdown();
}
3,scheduledThreadPool
ScheduledThreadPool(定时线程池): ScheduledThreadPool 是一个可以执行定时任务的线程池,它可以在指定的延迟时间后执行任务,或者以固定的频率重复执行任务。
private void scheduledThreadPool() {
//在上述示例中,我们创建了一个包含两个线程的定时线程池,并提交了两个任务给线程池执行。
// 第一个任务将在 5 秒后执行一次,第二个任务将每隔 2 秒执行一次。
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
executor.schedule(new Runnable() {
@Override
public void run() {
// 执行任务的逻辑
Log.e("tag","===============Task is running after 5 seconds");
}
}, 5, TimeUnit.SECONDS);
executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// 执行任务的逻辑
Log.e("tag","===============Task is running every 2 seconds");
}
}, 0, 2, TimeUnit.SECONDS);
// 等待任务执行
executor.shutdown();
}
4,singleThreadPool
SingleThreadPool(单线程池): SingleThreadPool 是一个只有一个线程的线程池, 所有任务都按照顺序执行,保证线程的安全性。
private void singleThreadPool() {
//在上述示例中,我们创建了一个只有一个线程的线程池,并提交了 10 个任务给线程池执行。
// 由于线程池中只有一个线程,保证了所有任务按照顺序执行。
ExecutorService executor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int task = i;
executor.execute(new Runnable() {
@Override
public void run() {
// 执行任务的逻辑
Log.e("tag","===============Task " + task + " is running");
}
});
}
executor.shutdown();
}
我们可以看到四种常用的线程池的创建,我们可以根据情况选择合适的线程池使用,同时我们也可以自定义线程池
3.3 自定义线程池
private ThreadFactory sIOThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"IOThread#"+mCount.getAndIncrement());
}
};
private ExecutorService mIOThreadPool = null;
//自定义线程池以及线程池工具类方法
private void testThread2() {
if(mIOThreadPool == null){
int CPU_COUNT = Runtime.getRuntime().availableProcessors();
int MAXIMUM_POOL_SIZE = CPU_COUNT*2 + 1;
long KEEP_ALIVE = 1000L;
mIOThreadPool = new ThreadPoolExecutor(
4,
Math.max(4, MAXIMUM_POOL_SIZE),
KEEP_ALIVE,
TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>(),
sIOThreadFactory);
}
for (int i = 0; i < 10; i++) {
final int task = i;
mIOThreadPool.execute(new Runnable() {
@Override
public void run() {
// 执行任务的逻辑
Log.e("tag","===============Task " + task + " is running");
}
});
}
mIOThreadPool.shutdown();
}
通过上面代码,我们可以通过ThreadPoolExecutor自定义线程池,这个时候,我们可以查看下上面四个线程池的源码
1,newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads,
nThreads,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2,cachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
3,scheduledThreadPool
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize,
Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS,
MILLISECONDS,
new DelayedWorkQueue());
}
4,singleThreadPool
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1,
1,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
我们可以看到常用的四种线程池和自定义线程池他们都是通过ThreadPoolExecutor来创建的,只是传入的参数不一样,接下来我们对ThreadPoolExecutor进行一些研究
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
我们看下ThreadPoolExecutor传入了7个参数,这些参数分别代表了
1,corePoolSize(核心线程数):线程池中保持运行的线程数量,即使线程是空闲的。如果任务数量超过核心线程数,线程池会创建新的线程来处理任务,直到达到最大线程数。
2,maximumPoolSize(最大线程数):线程池中允许的最大线程数量。如果任务数量超过最大线程数,并且任务队列已满,线程池会根据拒绝策略来处理无法执行的任务
3, keepAliveTime(线程空闲时间):当线程池中的线程数量超过核心线程数时,空闲线程的存活时间。超过这个时间,多余的线程会被销毁,保持线程池的大小不超过核心线程数。
4, unit(时间单位):用于指定 keepAliveTime 的时间单位,如 TimeUnit.SECONDS 表示秒。
5, workQueue(任务队列):用于存储等待执行的任务的阻塞队列。当线程池中的线程都在执行任务时,新提交的任务会被存储在任务队列中等待执行。
6,threadFactory(线程工厂):用于创建新线程的工厂。通过自定义线程工厂,可以对线程进行一些额外的设置,如命名、优先级等。
7,handler(拒绝策略):当任务无法被线程池执行时的处理策略。常见的拒绝策略有四种:AbortPolicy(默认),抛出 RejectedExecutionException;CallerRunsPolicy,由提交任务的线程来执行任务;DiscardPolicy,直接丢弃无法执行的任务;DiscardOldestPolicy,丢弃队列中最旧的任务,然后尝试重新提交当前任务。
线程池不需要重复创建,过多创建线程池容易发生资源泄露,因此可以使用单例模式创建一次即可。线程池的线程数理论上满足2*cpu核心数+1的时候性能最佳
线程池的最大线程数,可以根据执行的不同任务场景选择不同的最大任务数
1,CPU密集型任务:N(CPU 核心数)+1
这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。
一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
2,I/O密集型
这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。
线程池任务执行的顺序
我们可以看下ThreadPoolExecutor中execute的执行方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
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);
}
其中线程任务队列有哪些呢?
有界队列和无界队列
1,有界队列:可以指定最大容量
常见的有界队列有:
ArrayBlockingQueue:这是一个基于数组的有界阻塞队列。任务会按照先进先出的顺序进行执行。它可以指定一个固定的最大容量,超过这个容量的任务将无法加入队列,需要等待队列中的任务被取出后才能继续添加。如果线程池中的线程数小于核心线程数,新提交的任务将会创建新线程来执行,直到达到核心线程数。
2,无界队列:可以无限制地添加任务
常见的无界队列有:
LinkedBlockingQueue:这是一个基于链表的无界阻塞队列。任务会按照先进先出(FIFO)的顺序进行执行。如果任务数量超过核心线程数,超出的任务会被放入队列中,等待线程池中的线程来执行。
SynchronousQueue:这是一个没有缓冲的队列,任务会直接交付给线程进行执行。如果没有空闲的线程来处理任务,新提交的任务将会被拒绝。适用于任务量较小、执行时间短的场景
不同的任务队列对线程池的行为和特性有影响,主要体现在以下几点:
- 容量限制:
LinkedBlockingQueue是无界队列,可以无限制地添加任务;ArrayBlockingQueue是有界队列,可以指定最大容量;SynchronousQueue没有容量,无法缓冲任务。 - 任务顺序:
LinkedBlockingQueue和ArrayBlockingQueue都是按照先进先出的顺序执行任务,而SynchronousQueue没有缓冲,任务会直接交付给线程进行执行。 - 线程数控制:
LinkedBlockingQueue和ArrayBlockingQueue均通过控制线程数来处理任务,超过核心线程数的任务会被放入队列中等待执行。而SynchronousQueue在没有空闲线程时,会拒绝新的任务提交。
选择适合的任务队列取决于具体的应用需求和场景。如果任务量较大或者任务执行时间较长,可以选择无界队列;如果需要限制队列的容量,可以选择有界队列;如果希望直接将任务交付给线程执行,可以选择没有缓冲的队列。
那么对上面常见四种线程池的执行我们在了解了线程池参数的含义以后,也明白了他们的执行逻辑和使用场景
1,newFixedThreadPool
newFixedThreadPool的参数 ThreadPoolExecutor(nThreads, nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue())
核心线程数:nThreads
最大线程数:nThreads 核心和最大一样
线程空闲时间:0
时间单位:毫秒
任务队列:LinkedBlockingQueue,无界队列,先进先出执行
适用场景:需要控制并发线程数量且任务执行时间较长的情况,如网络请求、数据库操作等。
2,cachedThreadPool
cachedThreadPool的参数 ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue())
核心线程数:0
最大线程数:Integer.MAX_VALUE
线程空闲时间:60L
时间单位:秒
任务队列:SynchronousQueue,这是一个没有缓冲的队列,任务会直接交付给线程进行执行
适用场景:需要执行大量短期的任务,如并发处理较多的轻量级任务。
3,scheduledThreadPool
scheduledThreadPool的参数 super(corePoolSize,Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS,MILLISECONDS, new DelayedWorkQueue())
核心线程数:corePoolSize
最大线程数:Integer.MAX_VALUE
线程空闲时间:DEFAULT_KEEPALIVE_MILLIS,10L
时间单位:毫秒
任务队列:DelayedWorkQueue,
在Android线程池中,DelayedWorkQueue是一种实现了Delayed接口的延迟任务队列。它用于存放具有延迟执行时间的任务,并且会根据任务的延迟时间进行排序。DelayedWorkQueue是基于PriorityQueue实现的,所以它具有队列元素按照优先级排序的特性。
DelayedWorkQueue的主要特点如下:
- 延迟执行:DelayedWorkQueue中存放的任务具有延迟执行的特性。每个任务都有一个延迟时间,并且任务必须实现Delayed接口,提供getDelay(TimeUnit unit)方法,用于返回任务延迟的剩余时间。只有当延迟时间过去之后,任务才会被取出并执行。
- 优先级排序:DelayedWorkQueue基于PriorityQueue实现,任务队列中的任务按照优先级进行排序。优先级通过比较任务的延迟时间来确定,延迟时间较短的任务具有更高的优先级,会在队列中排在前面等待执行。
- 无界队列:DelayedWorkQueue没有固定的容量限制,可以无限制地添加任务。这意味着你可以根据需要动态地添加任务到队列中。
DelayedWorkQueue适用于需要延迟执行任务的场景,比如定时任务、任务调度等。通过使用DelayedWorkQueue,你可以方便地控制任务的延迟执行时间,并按照优先级顺序执行任务。
适用场景:需要按照固定顺序执行任务,保证任务的有序性。
4,singleThreadPool
singleThreadPool的参数 new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()))
核心线程数:1
最大线程数:1
线程空闲时间:0L
时间单位:毫秒
任务队列:LinkedBlockingQueue,无界队列,先进先出执行
适用场景:需要定时执行任务或者周期性执行任务的场景,如定时刷新数据、定时发送通知等。
4,线程池在其他三方库中的使用
4.1 线程池在OkHttp中的使用
在OkHttp的执行异步任务时,会把任务放到一个线程池中执行,我们可以在Dispatcher中看到源码
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
}
return executorServiceOrNull!!
}
这段代码是OkHttp源码中用于获取默认线程池的逻辑。在OkHttp中,默认线程池是通过ThreadPoolExecutor创建的,它的核心线程数为0,最大线程数为Int.MAX_VALUE,即可以处理非常大数量的任务。
默认线程池的使用场景主要是处理并发请求。在OkHttp中,每个请求都会被封装成一个Call对象,而Call对象内部会创建一个RealCall.AsyncCall对象,用于异步执行请求。当调用enqueue方法将请求加入到OkHttp的调度队列时,该请求会被添加到默认线程池中执行。
默认线程池的特点是它可以同时执行大量的任务,因为它的最大线程数设定为无限大。这对于需要高并发处理请求的场景非常有用,例如在并发情况下同时发送多个网络请求,或者处理大量的并行任务。
需要注意的是,默认线程池的最大线程数为无限大,这意味着它可以处理非常多的任务。但这也需要根据实际情况来评估系统的承载能力和资源消耗,以避免因过度并发导致系统负荷过大的问题。
总之,OkHttp中的默认线程池适用于需要高并发处理请求的场景,可以同时处理大量任务。但在实际使用中,还需要根据具体需求评估并发量和资源消耗,进行适当的调整和优化。
我们也可以自己设置OkHttp的线程池
// 创建带有自定义线程池的 OkHttpClient 实例
ExecutorService executorService = Executors.newFixedThreadPool(5); // 创建一个含有5个线程的线程池
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.dispatcher(new Dispatcher(executorService))
.build();
// 创建请求对象
Request request = new Request.Builder()
.url("https://www.example.com")
.build();
// 发送请求
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 请求失败处理逻辑
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// 请求成功处理逻辑
}
});
4.2 线程池在RxJava中的使用
Observable.just("Task")
.subscribeOn(Schedulers.io())
.subscribe(data -> {
// 在io线程池中执行耗时操作
// ...
});
其中Schedulers.io()使用的线程池什么,有些好奇,去看了下RxJava的源码,可以看到代码
在io.reactivex.internal.schedulers的IoScheduler类中
CachedWorkerPool(long keepAliveTime, TimeUnit unit, ThreadFactory threadFactory) {
this.keepAliveTime = unit != null ? unit.toNanos(keepAliveTime) : 0L;
this.expiringWorkerQueue = new ConcurrentLinkedQueue<ThreadWorker>();
this.allWorkers = new CompositeDisposable();
this.threadFactory = threadFactory;
ScheduledExecutorService evictor = null;
Future<?> task = null;
if (unit != null) {
evictor = Executors.newScheduledThreadPool(1, EVICTOR_THREAD_FACTORY);
task = evictor.scheduleWithFixedDelay(this, this.keepAliveTime, this.keepAliveTime, TimeUnit.NANOSECONDS);
}
evictorService = evictor;
evictorTask = task;
}
同样的RxJava也支持自己定义线程池
Observable.just("Task")
.subscribeOn(Schedulers.from(ThreadPoolUtils.getInstance().ioThreadPool))
.subscribe { data: String? ->
}
在RxJava中,Schedulers.io()调度器使用的是一个CachedThreadScheduler线程池,该线程池是通过io.reactivex.internal.schedulers.IoScheduler类中的静态方法create() 来创建的。
CachedThreadScheduler线程池是一个基于缓存的线程池,它会根据需要自动创建新线程,重用空闲线程,并在线程空闲一段时间后自动回收线程。这种线程池适合执行I/O密集型的任务和非计算密集型的任务。
CachedThreadScheduler线程池的参数如下:
- 核心线程数:0
- 最大线程数:
Integer.MAX_VALUE - 线程空闲时间:60秒
这意味着io.reactivex.internal.schedulers.IoScheduler使用的CachedThreadScheduler线程池在没有任务执行时不会保留任何线程,而是会在任务到来时动态创建线程,最多创建Integer.MAX_VALUE个线程。
需要注意的是,由于io.reactivex.internal.schedulers.IoScheduler类是RxJava内部实现的一部分,具体的线程池参数可能会因不同的RxJava版本而有所差异。因此,以上提到的参数仅作为一个常见的默认值,具体实现可以参考你所使用的RxJava版本的源码。
4.3,线程池在协程中使用
在 Android 协程中,Dispatcher.Default 使用的是 ThreadPoolExecutor,而 Dispatcher.IO 使用的是 Executor 接口的实现类 ThreadPoolExecutor,基于不同的参数配置来区分。
具体实现可以在协程库的源码中找到。以下是相关的源码示例:
Dispatcher.Default使用的线程池源码:
kotlin复制
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
@JvmField
internal val DefaultExecutor: ExecutorCoroutineDispatcher =
ForkJoinPool.commonPool().asCoroutineDispatcher()
可以看到,Default 使用了 ForkJoinPool.commonPool().asCoroutineDispatcher(),ForkJoinPool.commonPool() 返回的是 ForkJoinPool 实例。
Dispatcher.IO使用的线程池源码:
kotlin复制
public actual val IO: CoroutineDispatcher =
newFixedThreadPoolContext(Runtime.getRuntime().availableProcessors() * 2, "Dispatchers.IO")
@JvmField
internal val IOExecutor: ExecutorCoroutineDispatcher =
ForkJoinPool.commonPool().asCoroutineDispatcher()
可以看到,IO 使用了 newFixedThreadPoolContext 方法创建的线程池上下文,其中传入了 Runtime.getRuntime().availableProcessors() * 2 作为线程池的线程数。