查看原文
其他

Java 线程池,提高并发性能神器!

编程导航-小白条 面试鸭 2024-03-29

引言:Java 线程池一直是面试中备受关注的话题,但很多人只知道如何使用模板,却缺乏对核心参数和原理的深入理解。因此,本文将结合图文和代码,详细探讨Java线程池的工作原理,帮助读者更好地理解和应用线程池,在面对各种场景题时游刃有余。

题目

Java 线程池,提高并发性能神器!

推荐解析

什么是线程池?

线程池是管理一系列线程的资源池,用到了池化技术,像数据库连接池、HTTP 连接池都是池化技术的体现,主要目的是为了提高资源的利用率。

使用线程池的好处

1)降低资源消耗,通过重复利用已创建的线程来降低创建和销毁线程的消耗。

2)提高响应速度,因为当有任务到达时,线程已经被创建完毕,立刻能执行任务。

3)提高线程的可管理性,线程池可以批量管理线程,并且可以监控和调优,根据实际情况修改参数能将线程池效率发挥最大化。使用线程池可以让多个不相关的任务同时执行。

Executor 框架

Executor 框架在 JDK 1.5 之后引入,相比于直接用 Thread 的 Start 方法更好,方便管理,效率更高。

Executor 框架包括线程池管理,还提供线程工厂、任务队列和拒绝策略。

ThreadPoolExecutor 类

参数介绍

corePoolSize:核心线程数

maximumPoolSize:最大线程数

workQueue:任务队列

keepAliveTime:空闲存活时间

unit:keepAliveTime 参数的时间单位

threadFactory:线程工厂

handler:拒绝策略(核心线程满,任务队列满,最大线程数满,再有任务就会执行拒绝策略)

流程图

线程池的拒绝策略默认有以下 4 种:

1)AbortPolicy(中止策略)功能: 当触发拒绝策略时,直接抛出拒绝执行的异常,中止策略的意思也就是打断当前执行流程。

2)CallerRunsPolicy(调用者运行策略)功能:当触发拒绝策略时,只要线程池没有关闭,就由提交任务的当前线程处理。

3)DiscardPolicy(丢弃策略)功能:直接静悄悄的丢弃这个任务,不触发任何动作。

4)DiscardOldestPolicy(弃老策略)功能:如果线程池未关闭,就弹出队列头部的元素,然后尝试执行。

线程池创建两种方式

1)通过 ThreadPoolExecutor 构造函数。(推荐)

示例代码

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExecutorDemo {

    private static final int CORE_POOL_SIZE = 10;
    private static final int MAX_POOL_SIZE = 20;
    private static final int QUEUE_CAPACITY = 100;
    private static final Long KEEP_ALIVE_TIME = 1L;
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                new ThreadPoolExecutor.CallerRunsPolicy());

        for (int i = 0; i < 10; i++) {
            Runnable worker = new MyRunnable("" + i);
            executor.execute(worker);
        }
        executor.shutdown();
        while (!executor.isTerminated()) {
        }
        System.out.println("线程池已关闭");
    }
}

2)通过 Executor 框架的工具类 Executors 创建。

ThreadPoolExecutor 的类型

**FixedThreadPool**:该方法返回一个固定线程数量的线程池。

SingleThreadExecutor 该方法返回一个只有一个线程的线程池。

CachedThreadPool 该方法返回一个可根据实际情况调整线程数量的线程池。

**ScheduledThreadPool**:该返回一个用来在给定的延迟后运行任务或者定期执行任务的线程池。

Runnable 对比 Callable

Runnable 接口不会返回结果或抛出异常,而 Callable 接口可以,因此,可以根据只是需要返回值和抛出异常来判断使用哪个接口,根据实际场景去选择即可。

Execute 对比 Submit

execute 方法用于提交不需要返回值的任务。

submit 方法用于提交需要返回值的任务,线程池返回一个 Future 对象,调用 get() 方法获取返回值,该方法会阻塞当前线程直到任务完成。

Shutdown 对比 shutdownNow

shutdown:关闭线程池,线程池不再接受新任务,队列任务执行完毕后才关闭。

shutdownNow:关闭线程池,终止正在运行的任务,并停止排队任务,返回正在等待执行的列表。

isTerminated VS isShutdown

isShutDown:调用 shutdown 方法后返回 true。

isTerminated:调用 shutdown 方法后,当所有提交的任务完成,返回为 true。

注意事项

1)建议使用 ThreadPoolExecutor 的构造函数来声明,避免使用 Executors 类,可能发生 OOM 内存溢出。

2)使用有界队列,控制线程创建数量,防止 OOM。

3)监测线程池运行状态

4)合理配置线程池的参数,CPU 密集型:N+1,IO 密集型:2N

5)线程池不可以忘记关闭

6)不建议线程池放长时间执行的任务

7)推荐线程池以业务名称命令

其他补充

鱼聪明 AI 的回答:

鱼聪明 AI 地址:https://www.yucongming.com/

线程池

线程池是一种管理和复用线程的机制,它通过维护一定数量的线程,将任务分配给这些线程执行。线程池的目的是提高多线程应用程序的性能和效率。

使用线程池的好处

1)资源管理: 线程池可以限制同时执行的线程数量,避免过度消耗系统资源。

2)提高性能: 可以减少线程的创建和销毁开销,通过复用线程,降低线程创建的开销,提高性能。

3)任务队列: 线程池通常有一个任务队列,可以缓存任务,当有空闲线程时立即执行,避免任务堆积。

4)可控性: 可以通过调整线程池的大小、超时设置等参数,灵活控制线程的行为。

Executor 框架

Java提供了 java.util.concurrent 包,其中的 Executor 框架提供了一种将任务提交与任务执行分离的机制。Executor 提供了管理线程的标准接口,主要包括 ExecutorServiceScheduledExecutorService 等。

线程池的两种创建方式

1)通过工厂类 Executors

ExecutorService executorService = Executors.newFixedThreadPool(10);

2)ThreadPoolExecutor

   ThreadPoolExecutor executor = new ThreadPoolExecutor(
       corePoolSize,
       maximumPoolSize,
       keepAliveTime,
       TimeUnit.MILLISECONDS,
       new LinkedBlockingQueue<Runnable>()
   );

这种方式提供了更灵活的参数配置,可以自定义核心线程数、最大线程数、空闲线程的存活时间等。

线程池使用的注意事项:

1)合理配置线程池参数:

核心线程数、最大线程数、任务队列大小等参数应根据具体应用场景合理配置,避免资源浪费或任务堆积。

2)处理异常:

在任务执行过程中可能抛出异常,应该合理处理异常,确保异常不会导致整个线程池停止。

3)资源释放:

在应用程序关闭时,应该及时关闭线程池,释放相关资源,防止资源泄漏。

4)避免死锁:

在任务执行中避免使用同步操作,以免出现死锁情况。

5)监控和调优:

可以通过监控工具对线程池的运行情况进行监控,根据实际情况进行调优。

总的来说,合理使用线程池可以提高多线程应用程序的性能和可维护性,但需要注意参数配置、异常处理、资源释放等方面的细节。

知乎某同学的回答

线程池的主要好处包括:

1)降低资源消耗:通过复用已创建的线程而不是频繁地创建和销毁它们,可以减少线程相关的性能开销。这些开销包括CPU的上下文切换和内存分配等工作。

2)提高响应速度:线程池允许任务在到达时即刻执行,无需等待线程的创建,这有助于提升应用程序的执行效率。

3)提高线程的可管理性:线程是一种宝贵的资源,线程池提供了对线程统一分配、调优和监控的功能,使得线程的管理更加容易和高效。

4)有效控制最大并发线程数:线程池可以通过设置核心线程数和最大线程数量来控制并发的线程数量,这样可以避免由于无限制地创建和销毁线程导致的资源过度消耗和系统稳定性问题。

5)提供多种功能:线程池还支持定时执行、定期执行、单线程或并发数控制等功能,增加了其灵活性和实用性。

6)避免资源竞争和阻塞:通过限制并发线程的数量,线程池可以帮助避免过多的资源竞争,确保程序不会因线程数量的增加而导致阻塞。

推荐文章和书籍

文章:https://too.st/7Hk

书籍:《 Java 并发编程的艺术》

欢迎交流

在阅读本文之后,你应该对线程池的好处,如何创建,以及使用的注意事项等等有了一定的了解,在文末我将提出三个关于线程池的问题,欢迎小伙伴在评论区踊跃交流见解!

1)有哪些不同类型的线程池,它们之间有什么区别?

2)如何处理线程池中的异常?

3)什么是线程池的拒绝策略?如何选择和配置拒绝策略?


继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存