Java多线程并发笔记(4)线程池

前言

记录学习过程,仅作学习笔记
前面已经学了线程与锁机制
接下来学习对线程的管理 - 线程池,重点难点
Java3y公众号的教程
Java并发编程札记-(六)JUC线程池-02ThreadPoolExecutor实现原理

目录

  1. 概念
  2. Callable和Future
  3. ThreadPoolExecutor详解
    3.1. 属性
    3.2. 排队策略
    3.3. 构造方法
    3.4. 总结属性
  4. 线程池状态
  5. 执行任务
  6. 关闭线程池

线程池

概念

大佬的详解:Java并发编程札记-(六)JUC线程池-01概述

线程池是线程的集合,在没有任务时线程处于空闲状态,当请求到来:线程池给这个请求分配一个空闲的线程,任务完成后回到线程池中等待下次任务(而不是销毁)。这样就实现了线程的重用,可以大大的提升性能

平常使用线程都是直接创建,然后销毁

 CreatThread1 thread1=new CreatThread1();
 Thread myThread1=new Thread(thread1);
 myThread1.start();

为每个请求创建线程有一定的缺点:

  • 线程生命周期的开销非常高。每个线程都有自己的生命周期,创建和销毁线程所花费的时间和资源可能比处理客户端的任务花费的时间和资源更多,并且还会有某些空闲线程也会占用资源。

  • 程序的稳定性和健壮性会下降,每个请求开一个线程。如果受到了恶意攻击或者请求过多(内存不足),程序很容易就奔溃掉了。

线程池可以通过重复利用已创建的线程降低线程创建和销毁造成的消耗

jdk1.8中的线程池
Java多线程并发笔记(4)线程池_第1张图片

Executor

Executor是线程池顶级的接口,是线程池的基础
Executor提供了一种将“任务提交”与“任务执行”分离开来的机制(解耦)

Java多线程并发笔记(4)线程池_第2张图片
execute(Runnable)这么一个方法,用于执行已提交的Runnable任务

而在Executor下有多个子类

Java多线程并发笔记(4)线程池_第3张图片

ExecutorService

ExecutorService继承了Executor接口,用于提交一个用于执行的Runnable任务、试图停止所有正在执行的活动任务,暂停处理正在等待的任务、执行给定的任务
Java多线程并发笔记(4)线程池_第4张图片
ExecutorService提供了线程池生命周期管理的方法
Java多线程并发笔记(4)线程池_第5张图片

AbstractExecutorService

抽象类,继承ExecutorService接口,是ExecutorService的实现类
Java多线程并发笔记(4)线程池_第6张图片

ScheduledExecutorService和ScheduledThreadPoolExecutor

ScheduledExecutorService继承了ExecutorService,可安排在给定的延迟后运行或定期执行命令
ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,实现了ScheduledExecutorService

Java多线程并发笔记(4)线程池_第7张图片

ThreadPoolExecutor

提供一个可扩展的线程池实现,常用的线程池
需要详细了解

ForkJoinPool

JDK1.7中新增的一个线程池,与ThreadPoolExecutor一样,同样继承了AbstractExecutorService。ForkJoinPool是Fork/Join框架的两大核心类之一。与其它类型的ExecutorService相比,其主要的不同在于采用了工作窃取算法(work-stealing):所有池中线程会尝试找到并执行已被提交到池中的或由其他线程创建的任务。这样很少有线程会处于空闲状态,非常高效。这使得能够有效地处理以下情景:大多数由任务产生大量子任务的情况;从外部客户端大量提交小任务到池中的情况

CompeleteFuture、并发流等都是基于ForkJoinPool实现
ForkJoinPool

Callable和Future

前面创建线程时使用的是Runnable接口,它是无返回值的,还有有返回值的创建线程的方法,就是继承Callable接口

Callable接口与Runnable接口类似

Java多线程并发笔记(4)线程池_第8张图片
Future是一个未来对象,保存线程结果
在这里插入图片描述
执行Callable方式,需要Future的实现类的支持,用于接收运算结果。FutureTask是Future接口的实现类

package com.company.Thread.Callable;

import java.util.concurrent.*;

public class MyCallable implements Callable<Integer> {
    private int count=3;
    public  MyCallable(int count){
        this.count=count;
    }

    @Override
    public Integer call() throws Exception {
        return count;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //创建线程固定的线程池
        ExecutorService pool= Executors.newFixedThreadPool(2);

        //future接收返回值
        Future future=pool.submit(new MyCallable(100));
        Future future1=pool.submit(new MyCallable(200));

        System.out.println(future.get());
        System.out.println(future1.get());
        //线程池关闭
        pool.shutdown();
    }  
}

Java多线程并发笔记(4)线程池_第9张图片

ThreadPoolExecutor详解

属性

Java中常用的线程池有三个,最出名的当然是ThreadPoolExecutor,除此之外还有ScheduledThreadPoolExecutor、ForkJoinPool
Java多线程并发笔记(4)线程池_第10张图片
推荐使用Executor工厂方法配置,有3种常见的使用场景的配置
Executors.newCachedThreadPool()(自动线填海无界线程池)
Executors.newFixedThreadPool(int)(固定大小的线程池)
Executors.newSingleThreadExecutor()(单个后台线程)

当然也可以手动配置
手动配置需要了解属性:
Java多线程并发笔记(4)线程池_第11张图片Java多线程并发笔记(4)线程池_第12张图片
这个大佬的详解更清晰(Java3y公众号:线程池)


通过这些自定义属性,可以解析3个常用线程池配置,它们都在Executors类下

Java多线程并发笔记(4)线程池_第13张图片

  • newCachedThreadPool
    Java多线程并发笔记(4)线程池_第14张图片它设置了5个参数:
    核心池大小:无界值Integer.MAX_VALUE
    线程池中线程空闲时的活动时间:以秒为单元粒度的时间段
    SynchronousQueue 排队策略

创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。
调用 execute 将重用以前构造的线程(如果线程可用)。
如果现有线程没有可用的,则创建一个新线程并添加到池中。
终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
因此,长时间保持空闲的线程池不会使用任何资源

  • newFixedThreadPool
    Java多线程并发笔记(4)线程池_第15张图片5个参数:
    根据传入的nThreads定义corePoolSize和maximumPoolSize,即相等
    线程池中线程空闲时的活动时间:以毫秒为单元粒度
    LinkedBlockingQueue 排队策略

一个固定线程数的线程池,它将返回一个corePoolSize和maximumPoolSize相等的线程池

  • newSingleThreadExecutor
    Java多线程并发笔记(4)线程池_第16张图片5个参数:
    corePoolSize和maximumPoolSize相同为1,即固定单线程
    以毫秒为线程空闲活动时间
    LinkedBlockingQueue 排队策略

使用单个worker线程的Executor

排队策略

SynchronousQueue :
它将任务直接传输给工作队列workers,而不保持任务。如果不存在空闲线程,则会新建一个线程来执行任务
Java多线程并发笔记(4)线程池_第17张图片

LinkedBlockingQueue
无界队列,使用此队列会导致在所有corePoolSize线程都忙时新任务在队列中等待。这样,创建的线程就不会超过最大值(maximumPoolSize无意义)

Java多线程并发笔记(4)线程池_第18张图片ArrayBlockingQueue:
有界队列,少用

ThreadPoolExecutor构造方法

可以选择自定义ThreadPoolExecutor,它有4个构造方法
大致就是设置前面学到的各个属性
Java多线程并发笔记(4)线程池_第19张图片
最复杂的一个:
Java多线程并发笔记(4)线程池_第20张图片

  • 指定核心池线程数量corePoolSize
  • 指定最大池线程数量maximumPoolSize
  • 池中线程空闲时的活动时间keepAliveTime
  • 单元粒度的时间段TimeUnit
  • 阻塞队列BlockingQueue< Runnable>
  • 线程工厂ThreadFactory
  • 任务拒绝策略RejectedExecutionHandler

总结属性

线程数量corePoolSize、maximumPoolSize要点:

  • 如果运行线程的数量少于核心线程数量,则创建新的线程处理请求
  • 如果运行线程的数量大于核心线程数量,小于最大线程数量,则当队列满的时候才创建新的线程
  • 如果核心线程数量等于最大线程数量,那么将创建固定大小的连接池
  • 如果设置了最大线程数量为无穷,那么允许线程池适合任意的并发数量

池中线程空闲时的活动时间maximumPoolSize:

  • 当前线程数大于核心线程数,如果空闲时间已经超过了,那该线程会销毁

排队策略要点:

  • 同步移交:不会放到队列中,而是等待线程执行它。如果当前线程没有执行,很可能会新开一个线程执行。(SynchronousQueue )
  • 无界限策略:如果核心线程都在工作,该线程会放到队列中。所以线程数不会超过核心线程数(LinkedBlockingQueue:不具有预定义容量)
  • 有界限策略:防止资源耗尽,当最大线程数有限时,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷(ArrayBlockingQueue)

当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable) 中提交的新任务将被拒绝

拒绝任务策略:

  • ThreadPoolExecutor.AbortPolicy ,默认策略,处理程序遭到拒绝将抛出运行时
    RejectedExecutionException。
  • ThreadPoolExecutor.CallerRunsPolicy,线程调用运行该任务的 execute
    本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
  • ThreadPoolExecutor.DiscardPolicy,不能执行的任务将被删除。
  • ThreadPoolExecutor.DiscardOldestPolicy,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)

线程池状态

Java多线程并发笔记(4)线程池_第21张图片Java多线程并发笔记(4)线程池_第22张图片
线程池的5种状态:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED

ctl对象一共32位,高3位保存线程池状态信息,后29位保存线程池容量信息。线程池的初始化状态是RUNNING
Java多线程并发笔记(4)线程池_第23张图片

执行任务

package com.company.Thread.Callable;

import java.util.concurrent.*;

public class MyCallable implements Callable<Integer> {
    private int count=3;
    public  MyCallable(int count){
        this.count=count;
    }

    @Override
    public Integer call() throws Exception {
        return count;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //创建线程固定的线程池
        ExecutorService pool= Executors.newFixedThreadPool(2);

        //future
        Future future=pool.submit(new MyCallable(100));
        Future future1=pool.submit(new MyCallable(200));

        System.out.println(future.get());
        System.out.println(future1.get());
        //线程池关闭
        pool.shutdown();
    }
}

这个程序中启动线程池submit方法

/**
  * 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
  * 该 Future 的 get 方法在 成功 完成时将会返回 null。
  */
public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

submit又是调用execute方法

Java多线程并发笔记(4)线程池_第24张图片execute()分三种情况处理任务

case1:如果线程池中运行的线程数量 case2:如果线程池中运行的线程数量>=corePoolSize,且线程池处于RUNNING状态,且把提交的任务成功放入阻塞队列中,就再次检查线程池的状态,1.如果线程池不是RUNNING状态,且成功从阻塞队列中删除任务,则该任务由当前 RejectedExecutionHandler 处理。2.否则如果线程池中运行的线程数量为0,则通过addWorker(null, false)尝试新建一个线程,新建线程对应的任务为null。
case3:如果以上两种case不成立,即没能将任务成功放入阻塞队列中,且addWoker新建线程失败,则该任务由当前 RejectedExecutionHandler 处理

这就是Executor工厂方法配置线程池

关闭线程池

shutdown()方法和shutdownNow()方法
Java多线程并发笔记(4)线程池_第25张图片Java多线程并发笔记(4)线程池_第26张图片shutdown() 按过去执行已提交任务的顺序发起一个有序的关闭,但是不接受新任务
shutdownNow()尝试停止所有的活动执行任务、暂停等待任务的处理,并返回等待执行的任务列表

shutdown和shutdownNow详解

shutdown()和shutdownNow()的区别

  • 调用shutdown()后,线程池状态立刻变为SHUTDOWN,而调用shutdownNow(),线程池状态立刻变为STOP
  • shutdown()通过中断空闲线程、不接受新任务的方式按过去执行已提交任务的顺序发起一个有序的关闭,shutdownNow()无差别地停止所有的活动执行任务,暂停等待任务的处理。也就是说,shutdown()等待任务执行完才中断线程,而shutdownNow()不等任务执行完就中断了线程。

你可能感兴趣的:(Java多线程并发笔记)