线程组和线程池的基本用法

1.线程组

1.1创建线程组的方法

public class xianchengzu {public static void main(String[] args) {ThreadGroup group = new ThreadGroup("group");// 创建线程组时指定父线程组ThreadGroup parent = new ThreadGroup("parent");ThreadGroup child = new ThreadGroup(parent, "child");// 向线程组中添加线程Thread thread1 = new Thread(group,"thread1"){public void run(){System.out.println("我是" + group.getName() + "线程组的一个线程,名称为:"+ this.getName());}};thread1.start();// 通过runnable创建线程Thread thread2 = new Thread(group,new run());thread2.start();}
}class run implements Runnable{public void run(){System.out.println("我是实现runnable接口的线程");}
}

1.2线程组常用方法

// 获取线程组名称
String name = group.getName();// 获取线程组中活跃线程的估计数
int activeCount = group.activeCount();// 获取线程组中活跃子线程组的估计数
int activeGroupCount = group.activeGroupCount();// 中断线程组中所有线程
group.interrupt();// 设置线程组中所有线程的优先级
group.setMaxPriority(Thread.NORM_PRIORITY);// 枚举线程组中的线程
Thread[] threads = new Thread[group.activeCount()];
group.enumerate(threads); // 将线程组中的线程复制到数组中

2.线程池

线程池是一个包含了能提供相同功能的多个线程的集合。

2.1ThreadPoolExecutor

ava 线程池的核心实现是ThreadPoolExecutor,其构造方法包含以下核心参数:

public ThreadPoolExecutor(int corePoolSize,                // 核心线程数int maximumPoolSize,             // 最大线程数long keepAliveTime,              // 非核心线程空闲超时时间TimeUnit unit,                   // 超时时间单位BlockingQueue<Runnable> workQueue,// 任务阻塞队列ThreadFactory threadFactory,     // 线程工厂RejectedExecutionHandler handler // 拒绝策略
)

各参数的含义:

  • corePoolSize:线程池长期保持的线程数,即使线程空闲也不会销毁
  • maximumPoolSize:线程池允许创建的最大线程数
  • keepAliveTime:当线程数超过核心线程数时,多余线程的空闲存活时间
  • workQueue:用于存放等待执行的任务的阻塞队列
  • threadFactory:用于创建新线程的工厂
  • handler:当任务无法被处理时的拒绝策略
public class xianchengchi {public static void main(String[] args) {ThreadPoolExecutor pool01 = new ThreadPoolExecutor(3,5,500,TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(5));for(int i=0;i<10;++i){DoWork doWork = new DoWork(i);pool01.execute(doWork);System.out.println("我是线程池,当前池中的线程数为:"+pool01.getActiveCount()+ " 线程池的总容量为:"+pool01.getPoolSize());}for(int i=0;i<8;++i){System.out.println("我是01线程池,目前池内线程总数为:"+ pool01.getPoolSize() + " 等待队列中的任务为:"+pool01.getQueue().size());try{Thread.currentThread().sleep(100);}catch (InterruptedException e){e.printStackTrace();}}pool01.shutdown();}
}class DoWork implements Runnable {private int i;public DoWork(int i) {this.i = i;}@Overridepublic void run() {System.out.println("我是" + i + "号任务,我的任务执行完了");try{Thread.currentThread().sleep(100);}catch (InterruptedException e){e.printStackTrace();}}
}

上面代码创建了一个线程池pool01,核心线程数为3,最大线程数为5,空闲存活时间为500毫秒,等待队列为5。

为线程池添加10个线程,最初,线程池的核心线程数从0到3,此时新加入的任务会被放入等待队列中,如果等待队列也满了,才会继续增加线程池的线程数,如果线程池达到最大的线程数时继续加入线程,则会调用拒绝策略。

下面是一次运行的结果。

我是0号任务,我的任务执行完了
我是线程池,当前池中的线程数为:1 线程池的总容量为:1
我是线程池,当前池中的线程数为:2 线程池的总容量为:2
我是线程池,当前池中的线程数为:3 线程池的总容量为:3
我是1号任务,我的任务执行完了
我是线程池,当前池中的线程数为:3 线程池的总容量为:3
我是线程池,当前池中的线程数为:3 线程池的总容量为:3
我是2号任务,我的任务执行完了
我是线程池,当前池中的线程数为:3 线程池的总容量为:3
我是线程池,当前池中的线程数为:3 线程池的总容量为:3
我是线程池,当前池中的线程数为:3 线程池的总容量为:3
我是线程池,当前池中的线程数为:4 线程池的总容量为:4
我是8号任务,我的任务执行完了
我是线程池,当前池中的线程数为:5 线程池的总容量为:5
我是9号任务,我的任务执行完了
我是01线程池,目前池内线程总数为:5 等待队列中的任务为:5
我是4号任务,我的任务执行完了
我是3号任务,我的任务执行完了
我是5号任务,我的任务执行完了
我是6号任务,我的任务执行完了
我是01线程池,目前池内线程总数为:5 等待队列中的任务为:1
我是7号任务,我的任务执行完了
我是01线程池,目前池内线程总数为:5 等待队列中的任务为:0
我是01线程池,目前池内线程总数为:5 等待队列中的任务为:0
我是01线程池,目前池内线程总数为:5 等待队列中的任务为:0
我是01线程池,目前池内线程总数为:5 等待队列中的任务为:0
我是01线程池,目前池内线程总数为:5 等待队列中的任务为:0
我是01线程池,目前池内线程总数为:3 等待队列中的任务为:0进程已结束,退出代码为 0

2.2四种常用方法

除了前面介绍的通过ThreadPoolExecutor构建方法创建的线程池外,还有四种简化的创建线程池的方法,这几种方法不需要设置大量的参数,或者当不清楚怎么设置参数时可以参考这些方法。

2.2.1FixedThreadPool(固定大小的线程池)

  • 创建方式:通过Executors.newFixedThreadPool(int nThreads)方法创建,参数nThreads指定线程池中线程的数量。
  • 核心参数特点
    • corePoolSizemaximumPoolSize都等于创建时指定的线程数量,即线程池中的线程数量固定不变。
    • keepAliveTime为 0,因为线程不会被销毁,始终保持在池中。
    • 使用LinkedBlockingQueue作为任务队列,容量为Integer.MAX_VALUE,理论上可以存放无限多的任务。
  • 工作原理:当提交任务时,如果有空闲线程,就直接使用空闲线程执行任务;如果没有空闲线程,任务会被放入任务队列等待,直到有线程空闲。
  • 适用场景:适用于执行长期的、有稳定并发需求的任务,比如服务器端的请求处理,因为固定数量的线程可以保证系统资源的稳定消耗,避免因线程数量波动带来的性能问题。

2.2.2CachedThreadPool(可缓存的线程池)

  • 创建方式:通过Executors.newCachedThreadPool()方法创建。
  • 核心参数特点
    • corePoolSize为 0,maximumPoolSizeInteger.MAX_VALUE,即线程池中的线程数量可以根据任务的多少动态调整。
    • keepAliveTime为 60L,单位是TimeUnit.SECONDS,即非核心线程如果闲置 60 秒,就会被销毁。
    • 使用SynchronousQueue作为任务队列,这个队列不存储任务,每个插入操作必须等待另一个线程的移除操作。
  • 工作原理:当提交任务时,如果有空闲线程,就直接使用空闲线程执行任务;如果没有空闲线程,就创建新的线程来执行任务。当线程空闲时间超过 60 秒,就会被销毁,所以线程池中的线程数量会根据任务的提交情况动态增减。
  • 适用场景:适用于执行大量的短期异步任务,比如异步日志记录、异步数据处理等场景。由于它可以快速创建和销毁线程,能很好地应对突发的大量任务请求。

2.2.3SingleThreadExecutor(单线程的线程池)

  • 创建方式:通过Executors.newSingleThreadExecutor()方法创建。
  • 核心参数特点
    • corePoolSizemaximumPoolSize都为 1,即线程池中始终只有一个线程。
    • keepAliveTime为 0,线程不会被销毁。
    • 使用LinkedBlockingQueue作为任务队列,容量为Integer.MAX_VALUE
  • 工作原理:所有提交的任务都会按照顺序依次由这个唯一的线程来执行,前一个任务执行完后,才会执行下一个任务,保证了任务的串行执行。
  • 适用场景:适用于需要保证任务顺序执行,或者任务之间有依赖关系的场景,比如数据库的单线程操作,确保数据操作的一致性和顺序性 。

2.2.4ScheduledThreadPool(支持定时及周期性任务执行的线程池)

  • 创建方式:通过Executors.newScheduledThreadPool(int corePoolSize)方法创建,参数corePoolSize指定线程池中的核心线程数量。
  • 核心参数特点
    • corePoolSize是创建时指定的核心线程数量,maximumPoolSizeInteger.MAX_VALUE
    • keepAliveTime为 0,核心线程不会被销毁。
    • 使用DelayedWorkQueue作为任务队列,用于存储延迟执行的任务。
  • 工作原理:除了具备普通线程池提交任务执行的功能外,还支持定时执行任务(如延迟一定时间后执行)和周期性执行任务(如每隔固定时间执行一次)。
  • 适用场景:适用于需要定时执行或者周期性执行的任务,比如定时备份数据、定时发送邮件、周期性的系统状态检查等场景。

3.线程的异常处理

使用Thread类的线程或是实现了Runnable接口的线程需要在 run 方法中使用try-catch捕获错误,在主程序中捕获会失效,但是实现callable接口的线程可以在主程序中捕获错误。

原因是在Thread类中有一个接口 UncaughtExceptionHandler,它包含一个uncaughtException方法,该方法原意是专门对原本线程中没有捕获成功的异常进行最终捕获处理,同时,当线程未捕获异常而进入该方法时,所有抛出的异常都会被java虚拟机所忽略,即不再对外抛出。

由于java的Thread类默认使用uncaughtException进行空处理,而java虚拟机又会忽略该方法之后的抛出异常,因此我们经常看到的结果是内部线程发生异常时,在外层线程看来,是既不能成功对内部线程的异常进行catch,也不能获取其详细信息。

根本原因是:子线程和主线程是完全独立的执行流(拥有各自的调用栈),子线程中抛出的异常无法直接 “渗透” 到主线程的调用栈中

具体来说:        

  • 当我们通过thread.start()启动子线程时,子线程的run()方法会在一个全新的调用栈中执行(与主线程的调用栈完全分离)。
  • 如果run()方法中抛出未捕获的异常(比如RuntimeException),这个异常只会在子线程的调用栈中传播,不会影响主线程的执行流程。
  • 主线程的try-catch只能捕获主线程自身调用栈中产生的异常,无法 “跨线程” 捕获子线程的异常。

为什么 Callable 的异常可以在主程序捕获?

CallableRunnable的核心区别在于:Callable的异常会被线程池 “封装并保存”,通过Future对象传递给主线程

具体流程:

  1. Callablecall()方法允许声明抛出异常(包括受检异常),当它抛出异常时,线程池会捕获这个异常,并将其封装到Future对象中。
  2. 主线程调用future.get()获取结果时,如果Callable执行中抛出了异常,get()方法会将封装的异常以ExecutionException的形式抛出(ExecutionExceptiongetCause()方法可以获取原始异常)。
  3. 因此,主线程可以通过try-catch捕获ExecutionException,从而间接获取Callable中的异常。

1. 使用Runnable接口的线程异常处理示例

public class RunnableExceptionExample {public static void main(String[] args) {Runnable runnable = () -> {// 模拟抛出异常throw new RuntimeException("Runnable中的异常");};Thread thread = new Thread(runnable);try {// 这里直接在main方法中捕获,捕获不到异常thread.start(); } catch (Exception e) {System.out.println("在main方法中捕获Runnable异常: " + e.getMessage());}}
}

上述代码中,在main方法里直接捕获Runnable线程执行时抛出的异常是捕获不到的。如果要处理Runnable线程中的异常,需要在run方法内部进行捕获,修改代码如下:

public class RunnableExceptionHandleInRunExample {public static void main(String[] args) {Runnable runnable = () -> {try {// 模拟抛出异常throw new RuntimeException("Runnable中的异常"); } catch (Exception e) {System.out.println("在Runnable的run方法中捕获异常: " + e.getMessage());}};Thread thread = new Thread(runnable);thread.start(); }
}

2. 使用Callable接口的线程异常处理示例

public class FutureCatch {public static void main(String[] args) throws InterruptedException, ExecutionException {Callable<String> stringCallable = new subCallable();FutureTask<String> futureTask = new FutureTask<String>(stringCallable);Thread thread = new Thread(futureTask);thread.start();try{System.out.println(futureTask.get());}catch(Exception e){System.out.println("返回字符串失败了,下标有问题");}}
}class subCallable implements Callable<String> {public String call() throws Exception{String str = "我是测试字符串";String subStr = str.substring(6,10);return subStr;}
}

4.多线程的安全关闭

多线程的关闭设计其他线程的运行和稳定,所以一个线程并不是调用一个关闭的方法就能马上停止的,要考虑线程使用的资源是否已经释放、线程是否需要在退出服务前通知其它线程或进行一些准备工作。

public class CloseThread {public static void main(String[] args) throws InterruptedException, ExecutionException {Thread clost = new Thread(new waittoclose());clost.start();try{// 让主线程休眠Thread.sleep(2000);}catch (InterruptedException e){e.printStackTrace();}// 调用方法后isInterrupted() 变为true,并且抛出InterruptedExceptionclost.interrupt();}
}class waittoclose implements Runnable{@Overridepublic void run() {while(Thread.currentThread().isInterrupted()==false){try{System.out.println(Thread.currentThread().getName()+"正在运行");Thread.sleep(200);}catch(InterruptedException e){System.out.println("资源正在释放");System.out.println("线程关闭中");Thread.currentThread().interrupt();}}}
}

当线程在执行Thread.sleep() 时,如果收到中断信号(其他线程调用了它的 interrupt() 方法),会发生两件事:

  1. sleep() 会立即抛出 InterruptedException 异常,提前结束休眠
  2. 自动清除线程的中断状态(将中断标记设为 false

在Runnable接口中,try模块的Thread.sleep(200)在察觉到interrupt后,会按照上述抛出异常和清除中断状态,之后catch模板捕获异常,并进行善后操作,最后还要关闭一次线程,这样线程才能实现安全关闭。

5.自定义多线程异常处理

这里给一个简单的自定义异常:

public class ExceptionHandle implements Thread.UncaughtExceptionHandler {@Overridepublic void uncaughtException(Thread t, Throwable e) {System.out.println(t.getName() + "抛出了异常信息:" + e.toString());}
}
  • 实现了 Thread.UncaughtExceptionHandler 接口
  • 当线程抛出未捕获异常时,JVM会自动调用此方法

public class CatchExceptionFactory implements ThreadFactory {@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r);t.setUncaughtExceptionHandler(new ExceptionHandle()); // 关键设置return t;}
}
  • 实现了 ThreadFactory 接口
  • 为每个新创建的线程设置自定义异常处理器

class divide implements Runnable {@Overridepublic void run() {int a = 30;int b = 3;int step = 1;while(b >= 0) {System.out.println(a + "与" + b + "相除的结果为:");System.out.println(a / b); // 当b=0时这里会抛出ArithmeticExceptionb--;}System.out.println("结束运算");}
}
  • 包含一个会引发除零异常的任务
  • 当b递减到0时,a/b 会抛出 ArithmeticException

public class ExceptionTest {public static void main(String[] args) {// 使用自定义线程工厂创建线程池ExecutorService executorService = Executors.newCachedThreadPool(new CatchExceptionFactory());// 提交任务executorService.execute(new divide());}
}

运行结果(此时主程序并没有停止):

30与3相除的结果为:
10
30与2相除的结果为:
15
30与1相除的结果为:
30
30与0相除的结果为:
Thread-0抛出了异常信息:java.lang.ArithmeticException: / by zero

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/92280.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/92280.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

百度华为硬件笔试机试题-卷4

百度华为硬件笔试机试题-卷4 收集整理了以下30道选择题和判断题,涵盖电源管理、功率放大、半导体器件、数字逻辑、信号处理和电磁理论等领域。题目涉及复杂计算和分析,给出了参考答案和详细的解析,非常适合硬件工程师笔试机试准备。 选择题 1. 电源纹波主要测量什么值? …

38-TS之类型保护

关注大师不迷路,大师带你上高度~ 文章目录 前言 一、类型保护是什么? 二、使用步骤 1. 使用 typeof 操作符 2. 使用 instanceof 操作符 3. 自定义类型保护函数 4. 使用 in 操作符 总结 前言 关注大师不迷路,大师带你上高度~ 在前端开发中,JavaScript 的动态类型特性提供了…

win下安装labelimg

1、anconda安装python、qt的版本 conda create -n labelme python3.10.18 PyQt5 5.15.11 <pip> PyQt5-Qt5 5.15.2 <pip> PyQt5_sip 12.17.0 <p…

【Qt开发】常用控件(二) -> enabled

目录 1 -> 什么是 enabled 属性 2 -> API 3 -> 代码示例 3.1 -> 创建禁用状态按钮 3.2 -> 通过按钮切换按钮的禁用状态 1 -> 什么是 enabled 属性 在 Qt 中&#xff0c;enabled 是 QWidget 类的一个基础属性&#xff0c;它控制控件是否对用户输入做出响…

MySQL 配置性能优化赛:核心策略与实战技巧

在数据库性能优化领域,MySQL 配置调优如同一场精密的竞技比赛 —— 既要深刻理解数据库内核机制,又要根据硬件环境和业务场景灵活调整参数,最终在性能指标上脱颖而出。本文将围绕 MySQL 配置性能优化的核心维度,解析关键参数调优策略与实战经验。 一、性能优化的底层逻辑:…

C++ WonderTrader源码分析之自旋锁实现

一、介绍 在WonderTrader的文件SpinMutex.hpp定义了跨平台的自旋锁的实现。 二、实现原理 1、类 SpinMutex&#xff1a;自旋锁实现SpinMutex 是一个轻量级的自旋锁&#xff08;Spinlock&#xff09;实现&#xff0c;用于多线程之间保护临界区资源。自旋锁通过不断尝试获取锁而不…

【AI大模型】Spring AI 基于Redis实现对话持久存储详解

目录 一、前言 二、Spring AI 会话记忆介绍 2.1 Spring AI 会话记忆概述 2.2 常用的会话记忆实现方式 2.2.1 集成数据库持久存储会话实现步骤 2.3 适用场景 三、Spring AI基于内存会话记忆存储 3.1 本地开发环境准备 3.2 工程搭建与集成 3.2.1 添加核心依赖 3.3.2 添…

Numpy科学计算与数据分析:Numpy数据分析与图像处理入门

Numpy实战&#xff1a;从数据分析到图像处理 学习目标 通过本课程&#xff0c;学员将学会运用Numpy库进行数据分析和图像处理。学习如何使用Numpy进行数据的高效处理&#xff0c;以及如何利用Numpy进行基本的图像操作。 相关知识点 Numpy的数据分析和图像处理 学习内容 1…

Vue框架总结案例

目录 一、验证用户名是否已经被注册过 二、过滤器 三、图书管理系统 四、axios网络请求 一、验证用户名是否已经被注册过 1.案例 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><script src"j…

hyper-v虚拟机启动失败:Virtual Pci Express Port无法打开电源,因为发生错误,找不到即插即用设备

启动错误 今天启动某个hyper-v虚拟机时&#xff0c;启动失败了&#xff0c;大概的错误信息为&#xff1a;尝试更改“ubuntu_desktop_2204”的状态时应用程序遇到错误。Virtual Pci Express Port (实例 ID 0445948B-C377-4912-AEEB-58A3D45C5694): 无法开机&#xff0c;因…

CSS包含块与百分比取值机制完全指南

引言&#xff1a;为什么需要理解包含块&#xff1f; 在CSS布局的世界中&#xff0c;包含块(Containing Block) 是一个基础但至关重要的概念。它就像是一个隐形的参考框架&#xff0c;决定了元素如何定位、尺寸如何计算以及百分比值如何解析。许多CSS开发者在使用百分比单位时遇…

Numpy科学计算与数据分析:Numpy数组操作入门:合并、分割与重塑

Numpy数组操作实战 学习目标 通过本课程的学习&#xff0c;学员将掌握Numpy中数组的基本操作&#xff0c;包括数组的合并、分割以及重塑等技巧&#xff0c;能够灵活运用这些操作处理数据&#xff0c;为后续的科学计算和数据分析打下坚实的基础。 相关知识点 Numpy数组操作 …

11_Mybatis 是如何进行DO类和数据库字段的映射的?

11_Mybatis 是如何进行DO类和数据库字段的映射的&#xff1f; 假设 VideoAbnormalContentMapper.xml 文件有如下方法&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN&quo…

2025年渗透测试面试题总结-06(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 逻辑漏洞 一、三大高危业务逻辑漏洞及修复方案 1. 订单金额篡改&#xff08;参数操纵&#xff09; 2. 重…

SpringBoot激活指定profile的方式

题目详细答案在 Spring Boot 中&#xff0c;可以通过多种方式激活指定的 Profile&#xff0c;以便在不同的环境中使用不同的配置。在application.properties文件中激活可以在默认的application.properties文件中通过spring.profiles.active属性激活某个 Profile。# application…

Pytest项目_day10(接口的参数传递)

接口的参数传递 如果我们需要在一个测试用例中使用另一个测试用例中获得的数据&#xff0c;应该怎么办&#xff1f; 解决方案一&#xff1a;使用函数返回值 - 我们可以在另一个测试用例中使用return来返回所需的数据&#xff0c;并在其他的测试用例中调用该测试用例&#xff08…

深信服GO面试题及参考答案(上)

Go 和 Java 的特点和区别是什么? Go 和 Java 都是静态类型、编译型语言,但在设计理念、语法特性、并发模型等方面存在显著差异,具体如下: 从语言设计目标来看,Go 由 Google 开发,旨在解决大型系统开发中的复杂性,强调“简单、高效、并发”,语法简洁,摒弃了许多传统面向…

BGP笔记及综合实验

BGP基础一、BGP产生背景 - BGP定义&#xff1a;边界网关协议&#xff08;BGP&#xff09;是自治系统间的动态路由协议&#xff0c;属于外部网关协议&#xff08;EGP&#xff09;。 - 自治系统&#xff08;AS&#xff09;&#xff1a;由统一管理、运行同一IGP协议的路由器组成&a…

全栈:如何判断自己应该下载哪个版本的Tomcat

版本兼容性矩阵 https://tomcat.apache.org/whichversion.html https://tomcat.apache.org/download-11.cgi 介绍一下这些版本的不同点&#xff1a; 一、按系统选&#xff08;优先看这个&#xff09; 1.Windows 系统&#xff08;普通使用&#xff0c;非服务自启&#xff09…

Redis的Linux安装

可以直接命令下载 wget http://download.redis.io/releases/redis-5.0.4.tar.gz下载好之后解压缩&#xff0c;并且重命名为redis 由于redis是c语言编写的&#xff0c;所以我们需要先安装gcc&#xff0c;安装的命令如下&#xff1a;yum -y install gcc 安装成功后输入 : gcc -v…