Java——多线程高并发系列之线程池(Executor)的理解与使用

简介: Java——多线程高并发系列之线程池(Executor)的理解与使用

文章目录:


写在前面

Demo1(使用Executors创建线程池)

Demo2(使用ThreadPoolExecutor创建线程池)

关于ThreadPoolExecutor中的七大参数、四种拒绝策略

线程池的执行策略

写在前面


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


在真实的生产环境中,可能需要很多线程来支撑整个应用,当线程数量非常多时,反而会耗尽 CPU 资源。如果不对线程进行控制与管理,反而会影响程序的性能。线程开销主要包括: 创建与启动线程的开销;线程销毁开销;线程调度的开销;线程数量受限 CPU 处理器数量,线程池就是有效使用线程的一种常用方式。线程池内部可以预先创建一定数量的工作线程,客户端代码直接将任务作为一个对象提交给线程池,线程池将这些任务缓存在工作队列中,线程池中的工作线程不断地从队列中取出任务并执行。

JDK 提供了一套 Executor 框架,可以帮助开发人员有效的使用线程池


Demo1(使用Executors创建线程池)


package com.szh.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
 * 线程池的基本使用
 */
public class Test01 {
    public static void main(String[] args) {
        //创建有 5 个线程大小的线程池
        ExecutorService executorService= Executors.newFixedThreadPool(5);
        //向线程池中提交13个任务, 这13个任务存储到线程池的阻塞队列中,
        //线程池中这 5 个线程就从阻塞队列中取任务执行
        for (int i = 0; i < 13; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getId() + " 编号的线程正在执行任务,开始时间:"
                                    + System.currentTimeMillis());
                    try {
                        TimeUnit.MILLISECONDS.sleep(1000 * 3); //模拟任务执行时长
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        //关闭线程池
        executorService.shutdown();
    }
}

这里创建了固定大小为5的线程池,同时向线程池中提交了13个任务,那么每次执行都会由线程池中的这5个线程去线程池中的阻塞队列中取任务执行。

每次这5个子线程都是一起执行的,所以它们的开始时间可以看到是一样的。

Demo2(使用ThreadPoolExecutor创建线程池)


package com.szh.threadpool;
import java.util.concurrent.*;
/**
 *
 */
public class Test03 {
    public static void main(String[] args) {
        //用一个银行的例子来讲解这七大参数
        ExecutorService threadPool=new ThreadPoolExecutor(
                3, //指定线程池中核心线程的数量(两个常开业务窗口)
                5, //指定线程池中最大线程数量(总共五个窗口)
                2, //当线程池线程的数量超过 corePoolSize 时, 多余的空闲线程的存活时长, 即空闲线程在多长时长内销毁
                TimeUnit.SECONDS, //keepAliveTime 的时长单位
                new LinkedBlockingQueue<>(3), //任务队列, 把任务提交到该任务队列中等待执行(银行候客区的大小)
                Executors.defaultThreadFactory(), //默认线程池工厂
                new ThreadPoolExecutor.AbortPolicy()); //拒绝策略, 当任务太多来不及处理时, 如何拒绝
                /*
                    默认是 AbortPolicy() 会抛出异常
                    CallerRunsPolicy() 只要线程池没关闭, 会在调用者线程中运行当前被丢弃的任务
                    DiscardPolicy() 直接丢弃这个无法处理的任务
                    DiscardOldestPolicy() 将任务队列中最老的任务丢弃, 尝试再次提交新任务
                 */
        //向线程池中提交9个任务
        for (int i = 0; i < 9; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
        //关闭连接
        threadPool.shutdown();
    }
}

在定义线程池最大大小的时候,一般有两种策略CPU密集型和IO密集型,所谓CPU密集型,也就是,几核的CPU就定义为几,我的是八核,所以定义为8


Runtime.getRuntime().availableProcessors();// 获取CPU的核数。

IO密集型,就是判断程序中有多少个非常耗IO线程的程序,最大线程池的大小要大于这个值即可。


上面这个案例,我设定的线程池最大线程池数量为5,阻塞队列最大为3,加起来一共是8。也就是说最多可以容纳8个任务的存储。而我for循环中向线程池中提交了9个任务,在运行结果中可以看到,前8个可以正常执行,当执行到第9个任务的时候,因为线程池的核心线程池数量为39个任务显然已经超出,所以有3个任务会交给核心线程执行,9-3=6,其余6个会向阻塞队列中存储;然后线程池会判断阻塞队列示符已满,阻塞队列我设定最大为3,这个时候阻塞队列中最多只能容纳3个,所以此时任务余额:6-3=3,线程池面对其余3个任务会询问自己的最大线程池数量,这里我设定为5,因为之前核心线程池数量已经占用了3个,也就是说此时最大线程池数量还剩下5-3=2,那么线程池中最多只能只能再承受2个任务了,然而2<3,所以还有1个任务线程池是处理不了的,那么这个时候就会执行拒绝策略,我这里设定的是默认的拒绝策略,AbortPolicy直接抛出异常。


关于ThreadPoolExecutor中的七大参数、四种拒绝策略


七大参数。

int corePoolSize //核心线程池数量
int maximumPoolSize //最大线程池数量
long keepAliveTime //超时存活时间
TimeUnit unit //超时单位
BlockingQueue<Runnable> workQueue //阻塞队列
ThreadFactory threadFactory //线程工厂,用于创建线程
RejectedExecutionHandler handler //拒绝策略

而四种拒绝策略查看ThreadPoolExecutor的源码可知,它们四个其实就是ThreadPoolExecutor的四个静态内部类。


线程池的执行策略


1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。


2、当调用execute()方法添加一个任务时,线程池会做如下判断:

1)如果正在运行的线程数量小于corePoolSize(核心线程数量),那么马上创建线程运行这个任务;

2)如果正在运行的线程数量大于或等于corePoolSize(核心线程数量),那么将这个任务加入到阻塞队列;

3)如果这时候阻塞队列满了,而且正在运行的线程数量小于maximumPoolSize(最大线程数了),那么还是要创建线程运行这个任务;

4)如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize(最大线程数了),那么线程池会执行拒绝策略(四种,默认是AbortPolicy直接抛出异常),告诉调用者我不能再接受任务了

5)当一个线程完成任务时,它会从队列中取下一个任务来执行。

6)当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

相关文章
|
2天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
33 14
|
5天前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
34 13
|
6天前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
1月前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
XML Java 数据库连接
Java高并发秒杀系统【观后总结】(一)
在慕课网上发现了一个JavaWeb项目,内容讲的是高并发秒杀,觉得挺有意思的,就进去学习了一番。
286 0
Java高并发秒杀系统【观后总结】(一)
|
存储 SQL 缓存
Java高并发秒杀系统【观后总结】(四)
在慕课网上发现了一个JavaWeb项目,内容讲的是高并发秒杀,觉得挺有意思的,就进去学习了一番。
274 0
Java高并发秒杀系统【观后总结】(四)
|
SQL 缓存 NoSQL
Java高并发秒杀系统【观后总结】(三)
在慕课网上发现了一个JavaWeb项目,内容讲的是高并发秒杀,觉得挺有意思的,就进去学习了一番。
245 0
Java高并发秒杀系统【观后总结】(三)
|
JSON JavaScript 前端开发
Java高并发秒杀系统【观后总结】(二)
在慕课网上发现了一个JavaWeb项目,内容讲的是高并发秒杀,觉得挺有意思的,就进去学习了一番。
227 0
Java高并发秒杀系统【观后总结】(二)
|
前端开发 Java 数据库连接
Java高并发秒杀系统【观后总结】
项目简介 在慕课网上发现了一个JavaWeb项目,内容讲的是高并发秒杀,觉得挺有意思的,就进去学习了一番。 记录在该项目中学到了什么玩意.. 该项目源码对应的gitHub地址(由观看其视频的人编写,并非视频源代码):https://github.com/codingXiaxw/seckill 我结合其资料和观看视频的时候整理出从该项目学到了什么... 项目Dao层 日志记录工具: Mybatis之前没注意到的配置属性: 使用jdbc的getGeneratekeys获取自增主键值,这个属性还是挺有用的。
1668 0
|
1月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
109 17

热门文章

最新文章