• JAVA中线程池的定义及使用


    1、什么是线程及线程池?

    线程是操作系统进行时序调度的基本单元。

    线程池可以理解为一个存在线程的池子,就是一个容器,这个容器只能存在线程。这个容器有大小,可以放7,8个,也可以放3,4个。也可以把容器装满,但是都有一个最大值,比如说12个。比如说我这边线程池一般都装5个线程,最多能装12个。这个时候有五个人需要使用线程池,他就拿走了5个线程,然后在来两个人怎么办,他肯定没有线程可以使用,他必须等着那5个人使用完才行。但是我的池子能装12个,我只有5个线程怎么行,我肯定还得在在装几个线程,要不然人再多一点就不够了,这时候来了2个,我在生产2个线程,总数到7个,这个时候剩下2个人就不需要等待了,就可以直接使用。如果在来6个人呢,这个时候,我的池子里面可能只剩下5个线程的容量了,我能在生产5个线程但是,还有一个人得在哪等着才行。我也不能让人家漫无目的的等着啊,我找5个凳子吧,你们坐那等着,然后第一波五个人用完线程结束了,一下子腾出来了5个线程,剩下的一个人可以使用线程,这个时候依次又来了10个人,我的线程只有4个人可以使用,位置能坐五个人,剩下一个人怎么办,要不直接拒绝,我这边没有位,你要不先去别的地方看看,但是直接拒绝肯定很让人心里不舒服,我得在想几种拒绝策略。。。,我看我的线程池用的人还比较多,这么多人用,要是有人一直占着我的线程池怎么办,肯定得想个办法处理?要不就直接一个线程只能使用1分钟,使用完之后立刻回收,如果想在使用,重新排队等待。这样我的线程生意越做越好,只要有人用,他就一直跑。

    是不是有点像饭店或者是自助餐店,自助餐店是比较形象的,我的饭店里面只要有位置就可以坐人,达到最大的量,剩下的客户只能在门口等待了,饭店里面的客户走一个,来一个在外边等待的,如果等待的位置没有了,客户看看没位置了就直接走了,如果有的人特别想吃,就在哪多等一会。在饭店里面的客户吃的时间也不能太长(一般在没有位置的情况下),大概2个小时,吃完就要离开。

    根据以上我的描述,大概可以确定线程池里面有什么?

    装了多少个线程、能装多少线程、线程可以保留多长时间、线程等待区、如何拒绝、创建线程
    在这里插入图片描述

    1.1、为什么要使用线程?

    • 程序的运行必须依靠进程,进程的实际执行单元就是线程。
      • 系统内服务的调用。系统是依托于进程运行的,系统内有很多服务,服务之间有交互,服务的运行依托于线程运行。多服务运行依托于多线程运行。服务之间的调用及数据交换是依托于进程间的内存进行数据交互的,同时线程也可以构建自己的内存空间。依托于进程间的资源调度和数据交互。
      • 多线程可以提高程序的执行性能。例如,有个 90 平方的房子,一个人打扫需要花费 30 分钟,三个人打扫就只需要 10 分钟,这三个人就是程序中的“多线程”。
    • 在很多程序中,需要多个线程互相同步或互斥的并行完成工作。
    • 线程相比进程来说,更加的轻量,所以线程的创建和销毁的代价变得更小。
    • 线程提高了性能,虽然线程宏观上是并行的,但微观上却是串行。从CPU角度线程并无法提升性能,但如果某些线程涉及到等待资源(比如IO,等待输入)时,多线程允许进程中的其它线程继续执行而不是整个进程被阻塞,因此提高了CPU的利用率,从这个角度会提升性能。
    • 在多CPU或多核的情况下,使用线程不仅仅在宏观上并行,在微观上也是并行的。

    1.2、为什么要使用线程池?

    • 多线程可以提高程序的执行性能
      • 比如说吃自助餐,当餐位足够多的时候,人也足够多的时候,自助餐盈利也是最多了,同时也可以提供就餐率与客户满意度。如果有200个人吃饭,有一百个餐位的话,每个人平均吃1个小时,那200个人吃两个小时就吃完了。如果只有10个餐位的话,200个人需要吃20个小时左右,想一下如果剩下的在哪里焦急等待吃饭的客户心里多么的不开心。
      • 自助餐餐的餐位就是线程,当线程足够多的时候就可以满足更多的人吃饭,但是也不是说线程越多越好,毕竟不是一定每次都会有200个客户过来吃饭,就算有200个客户过来吃饭,也需要评估一下,饭店里面的厨师够不够,打扫卫生的阿姨能不能收拾过来,饭店里面的盘子够不够等基本的硬件因素。这个就相当于系统的配置一下,需要一些本质上的内存、CPU处理等一些硬件条件。
    • 创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率(只要线程一直执行就不会销毁)
      • 记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3,如果T1+T3>T2,那么是不是说开启一个线程来执行这个任务太不划算了!正好,线程池缓存线程,可用已有的闲置线程来执行新任务,避免了T1+T3带来的系统开销,当然一直存活的核心线程也会消耗CPU资源
    • 线程并发数量过多,抢占系统资源从而导致阻塞
      • 我们知道线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况,运用线程池能有效的控制线程最大并发数,避免以上的问题
    • 对线程进行一些简单的管理
      • 比如:延时执行、定时循环执行的策略等运用线程池都能进行很好的实现

    1.3、线程池的优点

    • 提高线程利用率
      • 保证存在业务是的时候使用,不存在业务的时候就释放掉,合理使用线程,避免资源浪费
    • 提高程序的响应速度
      • 由线程池统一管理的话,资源分配使用统一的调度池进行调度,出现使用线程的情况能避免线程的创建及销毁的耗时,可以直接使用线程。
    • 便于统一管理线程对象
      • 线程池可以保证线程的统一调配与管理。
    • 可以控制最大并发数
      • 服务器是有线程使用上限的,线程使用对资源也有很大的消耗,所以线程池能很好的控制线程资源,避免浪费。

    2、线程池在java中的使用

    ThreadPoolExecutor这个类是java中的线程池类,可以使用它进行线程的池化。

    // 根据上面的描述大概分析一下线程都需要什么及参数的解析
    // corePoolSize 核心线程数,就是上面说的装了多少个线程
    // maximumPoolSize 最大线程数,就是上面说的能装多少线程
    // keepAliveTime 存活时间,就是上面说的线程可以保留多长时间
    // TimeUnit 这个是时间单位,有时、分、秒、天等等,是存活时间的单位
    // BlockingQueue 这是一个等待队列,就是上面显示的线程等待区
    // ThreadFactory 线程工厂,就是上面描述的如何创建线程,由谁创建
    // RejectedExecutionHandler 拒绝策略,就是上面显示的如何拒绝,是直接拒绝还是婉拒
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory)
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler)
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    可以看到,其需要如下几个参数:

    • corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
    • maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
    • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
    • unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
    • workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
    • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
    • handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

    2.1、线程池的工作原理

    在这里插入图片描述

    2.2、线程池的java代码示例

    import java.util.concurrent.*;
    
    public class ThreadTest {
        public static void main(String[] args) {
            ExecutorService threadPoolExecutor = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory());
            for (int i = 0; i < 20; i++) {
                int finalI = i;
                threadPoolExecutor.submit( ()->{
                    System.out.println(Thread.currentThread().getName() + "========" + finalI);
                });
            }
            threadPoolExecutor.shutdown();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行结果:

    pool-1-thread-1========0
    pool-1-thread-3========2
    pool-1-thread-3========4
    pool-1-thread-2========1
    pool-1-thread-3========5
    pool-1-thread-2========8
    pool-1-thread-5========7
    pool-1-thread-1========3
    pool-1-thread-4========6
    Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@61e717c2 rejected from java.util.concurrent.ThreadPoolExecutor@66cd51c3[Running, pool size = 5, active threads = 2, queued tasks = 0, completed tasks = 7]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
    at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
    at com.halo.communication.ThreadTest.main(ThreadTest.java:10)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    执行的线程数超过了线程池可容纳的大小,线程池使用默认拒绝策略拒绝多余线程执行,然后开始出现异常处理。上面执行的线程数到thread-5,5是线程池的默认最大线程数。然后执行for循环20次,进行执行到8的时候出现异常,说明线程池已经超载满负荷执行,所以线程池执行拒绝策略。

  • 相关阅读:
    ORACLE常见错误编码大全
    物联网知识复习
    知识问答之初步入门-2
    pytorch迁移学习载入部分权重
    高新技术企业认定管理工作网
    优思学院|六西格玛中的概率分布有哪些?
    Vue3 set up 的使用
    冻结ResNet50前几层并进行迁移学习(PyTorch)
    第五十七章 CSP的常见问题 - 如何调试CSP页面?
    pandas连接oracle数据库并拉取表中数据到dataframe中、筛选当前时间(sysdate)到一天之前的所有数据(筛选一天范围数据)
  • 原文地址:https://blog.csdn.net/exception_class/article/details/127675637