线程池的使用和简单原理

参考文献:

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

1 简介

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

2 原理

2.1 内部逻辑结构

线程池的使用和简单原理_第1张图片

2.2 核心参数

参数 意义 说明
corePoolSize 核心线程数 默认情况,核心线程会一直存活,包括空闲状态
maximumPoolSize (能容纳)最大线程数 当已提交的任务数大于等于该值,后面新提交的任务会使用拒绝策略handler
keepAliveTime 非核心线程闲时存活期 超过该时长,处于闲置状态的非核心线程会被回收(当allowCoreThreadTimeOut(true)时,keepAliveTime也适用于核心线程)
unit keepAliveTime单位 TimeUnit.DAYS; TimeUnit.HOURS;TimeUnit.MINUTES; TimeUnit.SECONDS;TimeUnit.MILLISECONDS;TimeUnit.MICROSECONDS;
workQueue 任务队列 当corePoolSize个核心线程都运行时,新来的任务就放到这个队列里等待执行(就绪状态)
threadFactor 线程工厂接口 为线程池创建新线程

 **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<Runnable workQueue>,
                               ThreadFactory threadFactory )

2.3 任务执行逻辑

线程池的使用和简单原理_第2张图片

 补充说明:

  • 处理任务优先级:核心线程 > 任务队列 > 非核心线程 > 拒绝策略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 对比

线程池类型 池内 线程类型 池内 线程数 处理特点 应用场景 BlockingQueue
定长线程池FixedThreadPool 核心线程 核心线程数固定 ①核心线程不被回收②任务队列大小无限制 控制线程池最大并发数 LinkedBlockingQueue
定时线程池ScheduledThreadPool 核心线程,非核心线程, 核心线程数固定,非核心线程数无限制 非核心线程闲时立刻回收(keepAliveTime为0) 执行定时 / 周期性任务 DelayedWorkQueue
缓存线程池CachedThreadPool 非核心线程 非核心线程数无限制 ①有闲置线程,复用线程;否则新建线程;②一般,提交任务立刻执行③非核心线程闲时灵活回收 执行数量多、耗时少任务 SynchronousQueue
单例线程池SingleThreadExecutor 核心线程 1个核心线程 所有任务按FIFO顺序在一个线程中执行②不存在线程安全问题 适合单线程任务;不适合必须使用多线程的耗时任务 LinkedBlockingQueue

5 总结

线程池的使用和简单原理_第3张图片

你可能感兴趣的:(Java多线程)