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个小时,吃完就要离开。

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

装了多少个线程、能装多少线程、线程可以保留多长时间、线程等待区、如何拒绝、创建线程

What are the principles, usage, and performance optimization methods of Java thread pool?-LMLPHP

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<Runnable> 这是一个等待队列,就是上面显示的线程等待区
// 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)
登录后复制

What are the principles, usage, and performance optimization methods of Java thread pool?-LMLPHP

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

  • corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。

  • maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。

  • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。

  • unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。

  • workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。

  • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。

  • handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

2.1、线程池的工作原理

What are the principles, usage, and performance optimization methods of Java thread pool?-LMLPHP

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();
    }
}
登录后复制

执行结果:

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

以上就是What are the principles, usage, and performance optimization methods of Java thread pool?的详细内容,更多请关注Work网其它相关文章!

09-19 12:50