Android | 线程池的使用和简单原理

参考文献:

  • Android 多线程: 完全解析线程池ThreadPool原理&使用
  • Android开发——Android中常见的4种线程池(保证你能看懂并理解)
  • Java并发编程:线程池的使用

1 简介

 除了①线程池,使用线程还有三种方式,分别是 ②继承Thread类 ③实现Runnable接口④实现Callable接口,这三种方式最后都需要新建和销毁线程。在实际的高并发场景下,往往线程数多,每个线程执行任务耗时短。上面三种新建线程的方式,新建和销毁线程的时间消耗经常会比实际执行任务的耗时还要长,导致提交的任务不能立即响应执行,并且新建和销毁线程往往有一定的资源消耗;其次创建新线程的方式线程缺乏统一管理,容易出现线程阻塞的情况。所以这里我们引入了一种复用线程执行任务的方式-线程池,减少新建线程的次数。
 首先说说“任务”的概念,实现Runnable或Callable接口创建的对象只能当做一个可以在线程中运行的任务,不是真正意义上的线程,所以最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。

2 原理

2.1 内部逻辑结构

线程池逻辑结构图

2.2 核心参数

线程池核心参数

ThreadPoolExecutor类是线程池中最重要的类,可以通过在该类实例化时配置不同的参数传入构造器,来实现自定义线程池;

        // 创建线程池对象,通过 构造方法 配置核心参数
   Executor executor = new ThreadPoolExecutor( CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE,
                                                TimeUnit.SECONDS, sPoolWorkQueue,sThreadFactory );

        // 构造函数
    public ThreadPoolExecutor (int corePoolSize,int maximumPoolSize,long keepAliveTime,
                               TimeUnit unit,BlockingQueue,
                               ThreadFactory threadFactory )

2.3 任务执行逻辑

线程池任务执行流程图

 补充说明:

  • 处理任务优先级:核心线程 > 任务队列 > 非核心线程 > 拒绝策略handler;
  • 非核心线程空闲时间超过存活期keepAliveTime,就会被回收;一般情况,核心线程即使闲置也不会被回收;(除非当allowCoreThreadTimeOut(true)时,keepAliveTime也适用于核心线程);
  • 一般情况,线程池刚创建时,其中是没有线程的,只有新任务到来,才会创建线程执行该任务;

​ 对于线程池、核心线程、非核心线程自己的通俗理解:

​  线程池看成一个公司,核心线程可以看成是看成是公司的正式员工,非核心线程可以看成是实习生;无论是正式员工还是实习生在同一时刻只能执行一个任务。当有新任务时,老板首先找正式员工干活;如果正式员工都在干活时继续来新任务,那么多余任务就放到一个任务队列里,等有正式员工闲下来后接着干新任务;如果任务较多,超出正式员工能处理的数量,这时老板就考虑招几个实习生来干活,实习生处理任务队列满了之后新来的任务。如果任务太多了,那么老板就不再接单了,拒绝处理新来的任务。

​  一般情况,正式员工有任务到来就干活,没任务就空闲,不会被裁;实习生有任务到来就干活,没任务就空闲,空闲的时间太长了,老板不养闲人,就把这个实习生裁了。

3 基本使用

     // 1.创建线程池,通过配置核心参数,从而实现自定义线程池
   Executor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE,
                                  TimeUnit.SECONDS,sPoolWorkQueue,sThreadFactory);
    // 注:在Java中,已内置4种常用线程池,下面会详细说明

        // 2.向线程池提交任务:execute(),传入Runnable对象
    threadPool.execute(new Runnable() {
            @Override
            public void run() {
                ... // 线程执行任务
            }
        });

    // 3. 关闭线程池shutdown() 
  threadPool.shutdown();

shutdown()shutdownNow()区别:

  • shutdown():等待正在执行任务的线程执行完再关闭线程池;
  • shutdownNow():不等待立即关闭;

4 四类常用线程池

 根据参数的不同配置,Java内置了4种常用线程池,他们的参数已经配置好了:

  • 定长线程池(FixedThreadPool)
  • 定时线程池(ScheduledThreadPool )
  • 缓存线程池(CachedThreadPool)
  • 单例线程池(SingleThreadExecutor)

4.1 定长线程池FixedThreadPool

  • 特点:只有核心线程 & 不会被回收、线程数固定、任务队列无大小限制;

  • 应用场景:控制线程池最大并发数;

//创建 定长线程池 对象 & 设置线程池线程数固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//向线程池提交任务:execute()
fixedThreadPool.execute(() -> System.out.println("定长线程池->执行任务啦"));
//关闭线程池
fixedThreadPool.shutdown();

4.2 定时线程池ScheduledThreadPool

  • 特点:核心线程数固定、非核心线程数无限制(闲时立刻回收);

  • 应用场景:执行定时 / 周期性 任务

//创建 定时线程池 对象 & 设置线程池核心线程数固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//创建好Runnable类线程对象 & 需执行的任务
Runnable task1 = () -> System.out.println("定时线程池->执行任务啦");
// 向线程池提交任务:schedule()
scheduledThreadPool.schedule(task1, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task1,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
//关闭线程池
scheduledThreadPool.shutdown();

4.3 缓存线程池CachedThreadPool

  • 特点:只有非核心线程、线程数无限制;灵活回收空闲线程(具备超时机制,全部回收时几乎不占系统资源);无线程可用时新建线程;
  • 应用场景:执行数量多、耗时少任务
// 创建 可缓存线程池 对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//向线程池提交任务1:execute()
cachedThreadPool.execute(() -> System.out.println("可缓存线程池-执行任务1"));
//向线程池提交任务2:execute();复用线程
cachedThreadPool.execute(() -> System.out.println("可缓存线程池-执行任务2"));
//关闭线程池
cachedThreadPool.shutdown();

4.4 单例线程池SingleThreadExecutor

  • 特点:只有一个核心线程,保证所有任务按顺序在一个线程中执行,不存在线程安全问题;

  • 应用场景:适合单线程任务;不适合必须使用多线程的耗时任务,如数据库操作、文件操作等;

//创建 单例线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//向线程池提交任务:execute()
singleThreadExecutor.execute(() -> System.out.println("单例线程池-执行任务啦"));
//关闭线程池
singleThreadExecutor.shutdown();

4.5 对比

四类常见线程池对比

5 总结

线程池要点思维导图

你可能感兴趣的:(Android | 线程池的使用和简单原理)