线程池-------

可以以new Thread(()->{线程执行任务}).start();这种方式开启一个线程。当run()运行结束,线程会被GC释放。

在真实环境中,可能需要很多线程来支撑整个应用,当线程数量非常多时,反而会耗尽CPU资源

多线程可以最大力度的发挥多核cpu的计算能力,提高系统的吞吐量。如果我们不对线程加以管理与控制,反而会影响程序性能。线程的开销主要包括:

1、创建与启动线程的开销,创建线程还需要额外的分配栈空间,启动会有线程调度开销;

2、线程销毁的开销

3、线程调度的开销;上下文切换,通过系统的线程调度器调度线程

4、线程总数受限于cpu内核总数

线程池就是有效使用线程的一种常用方式:线程池内部可以预先创建一定数量的工作线程,客户端 代码直接将任务作为 一个对象提交给线程池,线程池将这些任务缓存在工作(阻塞)队列中,线程池的工作线程不断的从阻塞队列中取出任务并执行。

JDK对线程池的支持

JDK提供了一套Exceutor框架,能帮助开发人员

java.util.concurrent Interface Executuor(基本的线程池接口)

方法: void execut(Runnable command) //线程池提交任务

子接口ExecutuorService 继承 Executuor

void shutdown() //关闭线程池-》不再接收新的任务,线程池中已接收的任务是正常执行完毕

<T> Future<T> submit(Callable<T> task)

submit(Runnable command)

实现类 ThreadPoolExecutor 实现ExecutuorService

子接口 ScheduledExcetuorService 继承 ExecutuorService

ScheduledFuture<T> schedule(Runnable command,long delay,TimeUnit unit)//

实现类 ScheduledThreadPoolExecutor 实现ScheduledExcetuorService 并继承了

复制代码
ThreadPoolExecutor

类Executors 提供若干工厂方法 依赖 ExecutuorService

java 复制代码
//创建有5个线程大小的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
//向线程池中提交15个任务
for(int i = 0;i<15;i++){
    threadPool.execute(new Runnable(){
        @Override
        public void run(){
            
        
        try{
                Thread.sleep(3000);//模拟任务执行时长
        }catch(InterruptedException e){
                e.printStackTrace();
        }

    });
}



//创建一个有调度功能的线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
//在延迟 2 秒后执行任务, schedule( Runnable 任务, 延迟时长, 时间单位)
scheduledExecutorService.schedule(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getId() + "System.currentTimeMillis());
    }
}, 2, TimeUnit.SECONDS);

//以固定的频率执行任务,开启任务的时间是固定的, 在 3 秒后执行任务,以后每隔 5秒重新执行一次
/* scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getId() + "----在固定频率开启任务--" + System.currentTimeMillis());
            try {
                TimeUnit.SECONDS.sleep(3); //睡眠模拟任务执行时间 ,如果任务执行时长超过了时间间隔,则任务完成后立即开启下个任务
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
}, 3, 2, TimeUnit.SECONDS);*/


//在上次任务结束后,在固定延迟后再次执行该任务,不管执行任务耗时多长,总是在任务结束
后的 2 秒再次开启新的任务
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getId() + "----在固定频率开启任务---" + System.currentTimeMillis());
            try {
                TimeUnit.SECONDS.sleep(3); //睡眠模拟任务执行时间 ,如果任务执行时长超过了时间间隔,则任务完成后立即开启下个任务
            } catch (InterruptedException e) {
                e.printStackTrace();

            }
         }
}, 3, 2, TimeUnit.SECONDS);

线程池核心部分

查看 Executors工具类中newCachedThreadPool(),newSingleThreadPool(),newFixedTreadPool()y源码

java 复制代码
public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new                 
               SynchronousQueue<Runnable>());
}
/**该线程池在极端情况下,每次提交新的任务都会创建新的线程执行. 适合用来执行大量
耗时短并且提交频繁的任务
**/





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

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


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

// 当不传ThreadFactory 参数时,threadFactory为 Executors.defaultThreadFactory()
//ThreadPoolExecutor 构造方法
public ThreadPoolExecutor(int corePoolSize,
                int maximumPoolSize,
                        long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable>         
                   workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

底层都是使用ThreadPoolExecutor线程池,都是对ThreadPoolExecutor线程池的封装。

ThreadPoolExecutor的构造方法参数含义:

corePoolSize 指定线程池中核心线程的数量

maximumPoolSize 指定线程池中最大线程数量

keepAliveTime 当线程池中线程的数量超过corePoolSize时,多余的空闲线程的存活市场,即空闲线程在多长时长内销毁

unit 为keepAliveTime时长单位

workQueue 任务(阻塞)队列,把任务提交到该任务(阻塞)队列中等待执行

threadFactory 线程工厂,用于创建线程

handler 拒绝策略 当任务太多来不及处理时,如何拒绝
说明 :
workQueue 工 作 队 列 是 指 提 交 未 执 行 的 任 务 队 列 , 它 是 BlockingQueue 接口的对象 , 仅
用于存储 Runnable 任务 . 根据队列功 能分类, 在 ThreadPoolExecutor 构造方法中可以使用以下几
种阻塞 队列:
1、 直接提交队列 , 由 SynchronousQueue 对象提供 , 该队列没有 容量, 提交给线程池的任务不会被真
实的保存 , 总是将新的任务提交给线程执行, 如果没有空闲线程 , 则尝试创建新的线程 , 如果线程数量已
经达到 maxinumPoolSize 规定的最大值则执行拒绝策略 .
2、有界任务队列 , 由 ArrayBlockingQueue 实 现 , 在创建ArrayBlockingQueue 对象时 , 可以指
定一个容量. 当有任务需要执行时, 如果线程池中线程数小于 corePoolSize 核心线程数则创建新的
线程;如果大于 corePoolSize 核心线程数则加入等待队列 . 如果队列已满则无法加入, 在线程数小于
maxinumPoolSize 指定的最大线程数前提下会创 建 新 的 线 程 来 执 行 , 如 果 线 程 数 大 于
maxinumPoolSize 最大线程数则执行拒绝策略
3、无界任务队列 , 由 LinkedBlockingQueue 对象实现 , 与有界队列相比, 除非系统资源耗尽 , 否则无界
队列不存在任务入队失败的情况. 当有新的任务时 , 在系统线程数小于 corePoolSize 核心线程数则
创建新的线程来执行任务; 当线程池中线程数量大于corePoolSize 核心线程数则把任务加入阻塞队

4、 优先任务队列是通过 PriorityBlockingQueue 实现的 , 是带有任 务 优 先 级 的 队 列 , 是 一 个 特
殊 的 无 界 队 列 . 不 管是 ArrayBlockingQueue 队列还是 LinkedBlockingQueue 队列都是按照先
进先出算法处理任务的.在 PriorityBlockingQueue 队列中可以根据任务优先级顺序先后执行.

jdk提供了四种拒绝策略

AbortPolicy 策略 , 会抛出异常(默认)
CallerRunsPolicy 策略 , 只要线程池没关闭 , 会在调用者线程中运行当前被丢弃的任务
DiscardOldestPolicy 将任务队列中最老的任务丢弃 , 尝试再次提交新任务
DiscardPolicy 直接丢弃这个无法处理的任务
Executors 工具类提供的静态方法返回的线程池默认的拒绝策略是AbortPolicy 抛出异常
也可以自定义拒绝策略

ThreadFactory

线程池中的线程 就是 ThreadFactory来的.
ThreadFactory 是一个接口 , 只有一个用来创建线程的方法 :
Thread newThread(Runnable r);
当线程池中需要创建线程时就会调用该方法

监控线程池

ThreadPoolExecutor 提供了一组方法用于监控线程池
int getActiveCount() 获得线程池中当前活动线程的数量
long getCompletedTaskCount() 返回线程池完成任务的数量
int getCorePoolSize() 线程池中核心线程的数量
int getLargestPoolSize() 返回线程池曾经达到的线程的最大数
int getMaximumPoolSize() 返回线程池的最大容量
int getPoolSize() 当前线程池的大小
BlockingQueue<Runnable> getQueue() 返回阻塞队列
long getTaskCount() 返回线程池收到的任务总数

线程池扩展

可以自定义线程池继承ThreadPoolExecutor,重写afterExecute和beforeExecute方法

java 复制代码
ExecutorService  executorService = new ThreadPoolExecutor(10,10,0,TimeUnit.SECONDS,new LinkedBlockingQueue<>()){//内部类

    //重写BeforeExecute方法
    //重写afterExecute方法
    //重写 terminated方法    线程池退出时
}
在监控每个任务的开始和结束时,或者自定义一些其他增强的功能

ThreadPoolExecutor线程池提供了两个方法:

protected void afterExecute(Runnable r,Throwable t)//任务结束(任务异常退出)后执行

protected void beforeExecute(Thread t,Runnable r)//任务前执行

在ThreadPoolExecutor源码中定义了个内部类Worker,ThreadPoolExecutor线程池的工作线

程就是Worker类的实例,Worker类的实例在执行时也会调佣beforeExecute与afterExecute方法

线程池数量

一般来说,线程池大小需要考虑cpu数量,内存大小等因素

线程池大小 = CPU数量*目标cpu的使用率*(1+等待时间与计算时间比)

线程池死锁

如果在线程池中执行的 任务 A 在执行过程中又向线程池提交了任务 B, 任务 B 添加到了线程
池的等待队列中 , 如果任务 A 的结束需要等待任务 B 的执行结果 . 就有可能会出现这种情况 : 线程池
中所有的工作线程都处于等待任务处理结果, 而这些任务在阻塞队列中等待执行, 线程池中没有可以
对阻塞队列中的任务进行处理的线程 , 这种等待会一直持续下去, 从而造成死锁 .
适合给线程池提交相互独立的任务, 而不是彼此依赖的任务 . 对于彼此依赖的任务, 可以考虑分
别提交给不同的线程池来执行 .

线程池异常

在使用 ThreadPoolExecutor 进行 submit 提交任务时,有的任务抛出了异常,但是线程池并没有

进行提示,即线程池把任务中的异常给吃掉了,可以把 submit 提交改为 execute 执行,也可以对

ThreadPoolExecutor线程池进行扩展.对提交的任务(submit 方法)进行包装

相关推荐
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck1 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei1 小时前
java的类加载机制的学习
java·学习
励志成为嵌入式工程师2 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉3 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer3 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml43 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~3 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616883 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端