Java线程池相关知识

1.简介

线程池是指在初始化一个多线程应用程序过程中提供一个线程集合,在需要执行新的任务时重用这些线程而不是每次都新建一个线程,避免了创建和销毁线程的额外开销,提高响应速度.线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求.然而,增加可用线程数量是可能的.线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务;


1.1.线程池的主要特点

1>.线程复用

2>.控制最大并发数

3>.管理线程


1.2.线程池的优势

1>.降低资源消耗.通过重复利用已经创建好的线程降低线程创建和销毁造成的资源消耗;

2>.提高响应速度.当任务到达时,任务可以不需要等待线程创建就可以立即执行;

3>.提高线程的可管理性.线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控;


2.线程池如何使用?

1>.java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类;

如图:
在这里插入图片描述

2.1.JDK提供的线程池

2.1.1.newFixedThreadPool(int)

1>.固定线程数的线程池,可控制线程最大并发数,超出的线程(任务)会在队列中等待;

2>.案例:

public class MyThreadPoolDemo {

    public static void main(String[] args) {

        //创建一个固定线程数的线程池,线程池中有5个工作处理线程(真正的可以处理用户任务的线程)
        //无论外部提交了多少个任务,都是由线程池中创建的这几个线程池去处理,不会额外再去另创建线程
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        //模拟多个用户办理业务,每个用户就是一个来自外部的请求线程
        try {
            for (int i = 1; i <=10 ; i++) {
                //外部提交10个线程任务
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t 办理业务...");
                });
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //让所有的入队任务都执行完毕再去关闭线程池
            executorService.shutdown();
        }
    }
}
  • 执行结果:
    在这里插入图片描述

3>.源码分析:
在这里插入图片描述

  • 说明:

    ①.创建一个定长线程池,可控制线程最大并发数,超出的线程(任务)会在队列中等待;

    ②.newFixedThreadPool创建的线程池,他的corepoolsize和maximumpoolsize属性值是相等的,他底层使用的是LinkedBlockingQueue有界阻塞队列存放待执行的任务;


2.1.2.newSingleThreadExecutor()

1>.单个线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行;

2>.案例:

public class MyThreadPoolDemo {

    public static void main(String[] args) {

        //创建一个单线程的线程池,线程池中只要一个工作处理线程
        //所有的任务都是交给这一个工作处理线程去处理
        ExecutorService executorService=Executors.newSingleThreadExecutor();

        //模拟多个用户办理业务,每个用户就是一个来自外部的请求线程
        try {
            for (int i = 1; i <=10 ; i++) {
                //外部提交10个线程任务
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t 办理业务...");
                });
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //让所有的入队任务都执行完毕再去关闭线程池
            executorService.shutdown();
        }
    }
}
  • 执行结果:
    在这里插入图片描述
    3>.源码分析:
    在这里插入图片描述
  • 说明:

    ①.创建一个单线程化的线程池,他只会用唯一的工作线程来执行任务,保证所有的任务按照指定的顺序执行;

    ②.newSingleThreadExecutor创建的线程池,他的coresize和maximumpoolsize属性值都设置为1,他底层使用的LinkedBlockingQueue有界阻塞队列来存放待执行的任务;


2.1.3.newCachedThreadPool()

1>.带缓存功能的线程池,线程数不固定,如果线程池长度(/线程池中的线程数)超过任务处理需要,可灵活回收空闲线程;若无可回收,则新建线程;

2>.案例:

public class MyThreadPoolDemo {

    public static void main(String[] args) {

        //创建一个不确定线程数的线程池,线程数根据任务多少由线程池自己去决定(根据任务复杂度,根据cpu的性能)
        //如果任务处理过程需要耗时较长,则说明对实时性要求不高,那么就可以交给一个线程来处理
        //如果任务处理过程需要的时间较短,那么就说明对实时性要求很高,一个线程忙不过来,此时会创建多个线程
        ExecutorService executorService = Executors.newCachedThreadPool();

        //模拟多个用户办理业务,每个用户就是一个来自外部的请求线程
        try {
            for (int i = 1; i <= 10; i++) {
                //外部提交10个线程任务
                executorService.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 办理业务...");
                });
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //让所有的入队任务都执行完毕再去关闭线程池
            executorService.shutdown();
        }
    }
}
  • 执行结果:
    在这里插入图片描述

3>.源码分析:
在这里插入图片描述

  • 说明:

    ①.创建一个可缓存的线程池,如果线程池的长度超过了处理的需要,可以灵活回收空闲线程;若无可回收,则新建线程;

    ②.newCachedThreadPool创建的线程池,将corepoolsize设置为0,将maximumpoolsize设置为Integer.MAX_VALUE,底层使用的是SynchronousQueue单个元素的阻塞队列来存放待执行的任务,也就是说来了任务就创建线程去运行,当某个线程空闲超过60s,就销毁该线程;


2.1.4.newScheduledThreadPool(int)

1>.支持定时及周期性任务的线程池,线程数不固定;

2>.案例:

public class MyThreadPoolDemo {

    public static void main(String[] args) {

        //创建一个支持定时和周期性执行的固定线程数的线程池
        ExecutorService executorService=Executors.newScheduledThreadPool(5);

        //提交一个定时任务
        //参数一:要执行的任务
        //参数二:第一次延迟多久执行
        //参数三:之后每隔多久执行一次
        //参数四:时间单位
        ((ScheduledExecutorService) executorService).scheduleAtFixedRate(()->{
            System.out.println(Thread.currentThread().getName()+"\t 任务执行..."+new Date());
        },1,3,TimeUnit.SECONDS);
    }
}
  • 执行结果:
    在这里插入图片描述

3>.源码分析:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 说明:

    ①.创建一个支持周期性任务的的线程池,可以根据自定义的执行策略执行提交的任务;

    ②.newScheduledThreadPool创建的线程池,他底层使用的DelayedWorkQueue延迟阻塞队列来存放待执行的任务;

    ③.延迟队列中任务的执行不早于启用的时间(也就是说在还没有到达指定的延迟时间之前,延迟队列中的任务是不会执行的),但是对于启用后的时间,它们没有任何实时保证.然后按照提交的先进先出(FIFO)顺序启用与计划执行时间完全相同的任务;

    ④.如果提交的任务在运行之前被取消,则会抑制执行,默认情况下,此类取消的任务不会自动从工作队列中删除,直到其延迟过去为止.尽管这可以进行进一步的检查和监视,但也可能无限期地保留已取消的任务.为避免这种情况,请在setRemoveOnCancelPolicy(boolean)方法的参数设置为true,这将导致在取消时立即从工作队列中删除被取消的任务;


2.1.5.newWorkStealingPool(int)

这是java8新增的线程池,使用目前机器上可用的处理器作为他的并行级别!


注意:

在实际开发过程中不要使用JDK提供的Executors工具类创建线程池,因为底层不好控制,容易出现错误;需要自己手动利用ThreadPoolExecutor创建线程池!

①.FixedThreadPool和SingleThreadPool:
运行请求队列的长度为Integer.MAX_VALUE(约等于21亿),可能会堆积大量的请求,从而导致OOM(内存溢出)异常;

②.CacheThreadPool和ScheduledThreadPool:
允许创建的线程数为Integer.MAX_VALUE(约等于21亿),可能会创建大量的线程,从而导致OOM异常;


3.线程池7大参数

3.1.corepoolsize

①.线程池中常驻核心线程数(最多保留的线程数);

②.在创建了线程池之后,当有请求任务来了之后,就会安排线程池中的(已经创建的)核心线程去执行请求任务,可以理解为今日当值的线程;如果线程池中的核心线程数小于corepoolsize并且这些核心线程全部都在执行任务,即没有空闲的核心线程,那么就创建新的核心线程去处理任务;

③.当线程池中的(正在执行的)线程数量达到corepoolsize之后,就会把到达的(还未处理的)任务放到任务队列中;
④.核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态);


3.2.maximumpoolsize

①.线程池中能够容纳同时执行的最大线程数,这个值必须大于等于1;

②.如果当前线程池中的线程都在处理任务,且任务队列都已满了,此时如果再有任务提交,那么就需要在线程池中创建新的工作线程去处理工作队列中的任务(这些新创建的工作线程就是在corepoolsize线程之外的线程,此时线程池中的线程数(maximumpoolsize)就是corepoolsize线程数加上新创建的工作线程数),然后将最新提交的任务放到任务队列中;此时线程池中的线程数达到了maximumpoolsize,而且任务队列也满了,如果再有任务提交,那么此时线程池就会根据指定的拒绝策略去处理最新提交的这些任务;


3.3.keepalivetime

①.核心线程之外的线程(救急线程)的存活时间;

②.当前线程池中线程数量超过了corepoolsize时,当某些线程的空闲时间达到了keepalivetime值,那么这些多余的空闲的线程就会被销毁直到只剩下corepoolsize数量的线程为止;

③.默认情况下,之后当线程池中的线程数大于corepoolsize时keepalivetime才会起作用,直到线程池中的线程数量不大于corepoolsize;


3.4.timeunit

①.keepalivetime的(时间)单位;


3.5.workqueue

①.任务(阻塞)队列,存放被提交但尚未被执行的任务;


3.6.threadfactory

①.表示用来生成线程池中工作线程的线程工厂,用于创建线程,一般使用默认的线程工厂即可;


3.7.handler

①.拒绝策略,表示当任务队列满了且(正在执行的)工作线程数大于等于线程池的最大线程数(即任务请求数>=(maximumpoolsize+queue.seze+1))时,如何来拒绝请求要执行的runnable的策略 ;


4.线程池的底层工作原理

1>.流程图:
在这里插入图片描述
在这里插入图片描述
2>.说明:

①.创建线程池,提交一个并发任务给线程池;
(注意:线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务,即线程的创建是懒加载的!)

②.先判断线程池中所有核心线程是否都在执行任务(即判断当前正在处理任务工作线程数是否大于等于corepoolsize).如果不是,则新创建一个(corepoolsize)线程执行刚提交的任务,否则,则进入第③步;

③.如果核心线程池中所有的线程都在执行任务,那么需要判断当前任务阻塞队列是否已满.如果队列未满,则将提交的任务放置在阻塞队列中,等待corepoolsize中的工作线程进行处理;否则,进入第④步;

④.如果任务阻塞队列也满了,那么需要判断当前线程池中所有的线程(maximumpoolsize)是否都在执行任务(即判断当前正在处理任务的工作线程数是否大于等于最大线程数(maximumpoolsize)),如果没有,则需要额外创建一个新的(非corepoolsize)线程来执行任务;否则,则任务交给饱和策略进行处理;

⑤.当(corepoolsize中)一个线程完成任务时,他会主动从任务阻塞队列中取出一个任务来执行;

⑥.当一个(非corepoolsize)线程无事可做且超过一定的时间(keepalivetime)时,线程池会判断:

如果当前线程池中所有运行的线程数大于corepoolsize,那么这个线程就会被停掉;所以当线程池中所有的任务都执行完成之后,线程池中的线程数会缩减到corepoolsize的大小;


5.线程池的4中拒绝策略

5.1.是什么?

当任务阻塞队列已满,再也塞不下新的任务,同时线程池中的所有的线程(即corepoolsize线程+非corepoolsize线程=maximumpoolsize)都在处理任务,无法为新任务服务.这时候我们就需要用到拒绝策略机制合理的处理新的任务;


5.2.四种拒绝策略

5.2.1.AbortPolicy(默认)

直接抛出RejectedExecutionException异常阻止系统正常运行


5.2.2.CallerRunsPolicy

"调用者运行"他是一种调节机制,该策略既不会抛弃任务,而不会抛出异常,而是将某些任务回退到调用者(一般是main线程,让main线程去处理),从而降低新任务的流量;


5.2.3.DiscardOldestPolicy

抛弃任务队列中等待最久的任务(即最开始添加的队列头部的任务,空一个位置出来),然后将当前任务添加到任务队列中尝试再次提交当前任务,如果执行程序已关闭,则会丢弃该任务;


5.2.4.Discardpolicy

直接丢弃(最新提交)任务,不予任何处理也不抛出异常.如果允许任务丢失,这是最好的一种方案;


6.自定义一个线程池

在实际开发过程中建议不要使用JDK提供的方式创建线程,因为在请求量非常大的情况下会出现OOM,我们需要手动实现一个线程池;

/**
 * 自定义一个线程池
 */
public class MyThreadPoolDemo {

    public static void main(String[] args) {
        
        //线程池所需要的参数
        int corePoolSize = 2;
        int maximumPoolSize = 5;
        long keepAliveTime = 3L;
        TimeUnit timeUnit = TimeUnit.SECONDS;
        BlockingQueue<Runnable> blockingQueue = new LinkedBlockingDeque<Runnable>(3);
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

        //自定义线程池
        ExecutorService threadPool = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                timeUnit,
                blockingQueue,
                threadFactory,
                handler);

        //模拟多个用户办理业务,每个用户就是一个来自外部的请求线程
        try {
            //往线程池中提交任务请求,根据上面的配置,线程池可以接受的最大请求任务数为:maximumpoolsize+queue.size
            //一旦超过这个数,就会使用拒绝策略处理多余的请求任务
            for (int i = 0; i < 10; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 办理业务...");
                });
            }
        } finally {
            //任务执行完毕,关闭线程池
            threadPool.shutdown();
        }
    }
}

7.线程池配置合理线程数

7.1.CPU密集型

①.CPU密集型的意思是指任务需要大量的运算,而没有阻塞,CPU一直全速运行;

②.CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),现在人们使用的电脑都是多核CPU,基本上不存在单核的了;

③.CPU密集型任务要配置尽可能少的线程数量: maximumpoolsize=CPU核(心)数+1;

注意:查看本机CPU核心数代码:

System.out.println(Runtime.getRuntime().availableProcessors());


7.2.IO密集型

①.IO密集型的意思是指任务需要大量的IO,即大量的阻塞;

②.在单线程上运行IO 密集型任务会导致浪费大量的CPU运算能力在等待(阻塞),所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间;

③.由于IO密集型任务线程并不是一直在执行任务,则应该配置尽可能多的线程:
maximumpoolsize=CPU核(心)数 * 2;

④.IO密集型时,大部分线程都被阻塞,因此需要多配置线程数:
maximumpoolsize: CPU核(心)数/(1-阻塞系数),阻塞系数一般在0.8~0.9之间;

比如4核CPU,那么最终配置4/(1-0.9)=40个线程数;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答:Java线程池Java中的一个重点知识,并且在Java的工作中经常会遇到,因此在面试中也是必问的面试题目。以下是一些常见的Java线程池面试题: 1. 谈谈什么是线程池? 2. 为什么要使用线程池? 3. 你们哪些地方会使用到线程池? 4. 线程池有哪些作用? 5. 线程池的创建方式有哪些? 6. 线程池底层是如何实现复用的? 7. ThreadPoolExecutor核心参数有哪些? 8. 线程池创建的线程会一直在运行状态吗? 9. 为什么阿里巴巴不建议使用Executors? 10. 线程池的底层实现原理是什么? 11. 线程池队列满了,任务会丢失吗? 12. 线程池的拒绝策略类型有哪些? 13. 线程池如何合理配置参数? 这些问题涵盖了线程池的基本概念、使用场景、实现原理以及相关的配置和策略等方面的知识。了解这些问题能够帮助面试者更好地理解和应用Java线程池。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [java线程池面试题有哪些?java线程池常见面试题](https://blog.csdn.net/muli525/article/details/123553744)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [(一)【Java精选面试题】线程池底层实现原理(含答案)](https://blog.csdn.net/qq_30999361/article/details/124924343)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值