Android/Java 线程池笔记

一、为何要使用线程池

在Java中,要使用多线程,除了使用new Thread()之外,还可以使用线程池ExecutorService。

// 使用Thread
Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
        // ...
    }
});
t.start();

// 使用线程池
ExecutorService es = Executors.newSingleThreadExecutor();
es.execute(new Runnable() {
    @Override
    public void run() {
        // ...
    }
});

线程池主要解决了两个问题:

  1. 频繁创建销毁线程的开销
  2. 任务的管理

在异步任务比较多时,创建、销毁线程会占用很多系统资源;这时候,使用线程池,就可以实现线程的复用,让人专注于任务的实现,而不是管理线程。

二、线程池简介

1. 什么是线程池

线程池(本文特指ThreadPoolExecutor类)顾名思义,就是一个装了线程的池子。线程池创建和管理若干线程,在需要使用的时候可以直接从线程池中取出来使用,在任务结束之后闲置等待复用,或者销毁。
线程池中的线程分为两种:核心线程和普通线程。核心线程即线程池中长期存活的线程,即使闲置下来也不会被销毁,需要使用的时候可以直接拿来用。而普通线程则有一定的寿命,如果闲置时间超过寿命,则这个线程就会被销毁。

查看ThreadPoolExecutor类的其中一个典型的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

线程池的具体行为和几个参数有关:

  • 核心数 corePoolSize
    线程池中核心线程的数量。
  • 最大容量 maximumPoolSize
    线程池最大允许保留多少线程。
  • 超时时间 keepAliveTime
    线程池中普通线程的存活时间。

2. 线程池的使用

线程池的一般使用步骤如下:

使用Executors中的工厂方法来获取ExecutorService实例;
使用ExecutorService的execute(runnable)或者submit(runnable)方法来添加任务。

ExecutorService es = Executors.newSingleThreadExecutor();
    es.execute(new Runnable() {
        @Override
        public void run() {
            String response = new HttpUtil().get("http://littlefogcat.top");
            System.out.println(response);
        }
    });

3. 线程池的分类

在Executors工厂类中提供了多种线程池,典型的有以下四种:

1. SingleThreadExecutor 单线程线程池

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

核心线程数为1,最大线程数为1,也就是说SingleThreadExecutor这个线程池中的线程数固定为1。使用场景:当多个任务都需要访问同一个资源的时候。

2. FixedThreadPool 固定容量线程池

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

核心线程数为n,最大线程数为n。使用场景:明确同时执行任务数量时。

3. CachedThreadPool 缓存线程池

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

核心线程数为0,最大线程数无上限,线程超时时间60秒。使用场景:处理大量耗时较短的任务。

4. ScheduledThreadPool 定时线程池

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }


/*
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
*/

核心线程数自定,最大线程数无上限。使用场景:处理延时任务。

可以看到,这四个方法都返回了一个ThreadPoolExecutor对象(ScheduledThreadPoolExecutor是其子类),仅仅是其中的参数略有不同。所以接下来就对ThreadPoolExecutor类进行解析。

4. ExecutorService.submit()与Executor.execute() 两个方法的相同与不同之处

相同之处:

Both submit() and execute() methods are used to submit a task to Executor framework for asynchronous execution.
submit和execute方法均可以想线程池中提交一个任务,让线程池来异步执行这个任务
Both submit() and execute() can accept a Runnable task.
两个方法均可以接受Runnable类型的任务
You can access submit() and execute() from the ExecutorService interface because it also extends the Executor interface which declares the execute() method.
从ExecutorService接口中均可以调用submit和execute方法,但是submit方法是在ExecutorService接口中定义的,而execute方法是在Executor接口中定义的

不同之处:

The submit() can accept both Runnable and Callable task but execute() can only accept the Runnable task.
submit方法既能接受有返回结果Callable类型和没有返回结果的Runnable类型,而execute方法只能结构没有返回结果的Runnable类型
The submit() method is declared in ExecutorService interface while execute() method is declared in the Executor interface.
submit方法是定义在ExecutorService接口中的,而execute方法是定义在Executor接口中的
The return type of submit() method is a Future object but return type of execute() method is void.
submit方法的返回值是一个Future,而execute方法的返回值是void
对于异常的处理
使用submit方式提交的任务若在执行的过程中抛出了异常的话,异常信息会被吃掉(在控制台中看不到),需要通过Future.get方法来获取这个异常;使用execute方式提交的任务若在执行的过程中出现异常的话,异常信息会被打印到控制台

参考:
https://zhuanlan.zhihu.com/p/143484740
https://www.imangodoc.com/178483.html


   转载规则


《Android/Java 线程池笔记》 pglprome 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
POSIX 定时器_QNX实现 POSIX 定时器_QNX实现
一、POSXI接口定时器_QNX实现设置定时器,并设置通知方式,内核以signal的方式通知到时 #include "Handler.hpp" #include <iostream> #include <memory> #inc
2022-12-21
下一篇 
STL list容器 .size()返回数值错误 STL list容器 .size()返回数值错误
前两天写缓存代码时,遇见了一个我认为是STL的bug,如图所示,t2_clean是我定义的一个std::list对象,通过IDE我们可以看到此时该容器中有两个节点,分别对应的下标是0和1,此时我们调用.size()方法,理应获得的值是2,但
2020-08-07
  目录