当前位置: 首页 > news >正文

多线程和线程池的理解运用

引言:这是一篇关于线程的博客,会基于这个开辟出一个关于多线程和线程池的专题,包括了使用,底层源码解析以及一些相关的面试题的理解。

可能对于初学者来说,线程池和多线程还是理解的不是很清楚。

多线程

(1)多线程是一种并发的执行机制,指的是在程序中一个进程运行多个线程,来提高资源的利用率和执行的效率。

(2)每个线程有独立的执行路径,但是使用的内存是同一个进程的内存空间,比如堆、方法区。

(3)多线程的目的是让程序能够并发执行多个任务。

创建多线程的方法

(1)通过类extends thread,重写run方法,创建对象,.start()方法启动线程。

优缺点:继承thread类,使用难度低,简单方便,但是Java属于单继承,若创建多线程通过继承的方法来实现,无法继承其他类,线程任务和线程进行了耦合,灵活性低。

public class NewThread extends Thread {@Overridepublic void run() {for (int i=0;i<5;i++){System.out.println("子线程执行:"+ i);}}public static void main(String[] args) {NewThread t=new NewThread();t.start();for (int i=0;i<5;i++){System.out.println("主线程执行:"+i);}}
}

(2)通过implements Runnable来实现run() 方法,同时创建实例,将实例传入thread对象中。

优缺点:通过implements 解决了无法多继承的弊病,实现了功能的解耦,但是run方法没有返回值,无法直接获得线程的返回值,同时无法知晓线程的抛出的问题信息。

public class NewThread implements Runnable{@Overridepublic void run() {for (int i=0;i<5;i++){System.out.println("这是子线程:"+i);}}public static void main(String[] args) {NewThread t=new NewThread();Thread tt=new Thread(t);tt.start();for (int i=0;i<5;i++){System.out.println("这是主线程:"+i);}}

(3)通过Callable接口+FutureTask类,与Runnable相似,但是有返回值,且可以抛出异常。

优缺点:首先同样避免单继承,同时可以获取返回值,抛出异常,但是在获取返回结果的时候,有可能会将线程阻塞。

public class NewThread implements Callable {@Overridepublic Integer call() throws Exception {int sum=0;for (int i=0;i<=5;i++){sum+=i;}return sum;}public static void main(String[] args) throws Exception {NewThread t=new NewThread();FutureTask<Integer> task=new FutureTask<>(t);Thread th=new Thread(task);th.start();int sum=task.get();System.out.println("子线程的计算结果是:"+sum);}
}

(4)通过线程池的技术来实现,其可以高效的管理多个线程,创建,删除,复用等。通过Executors这个工具类

public class NewThread{public static void main(String[] args) {ExecutorService ex= Executors.newFixedThreadPool(3);for (int i=0;i<5;i++){ex.submit(new Runnable(){@Overridepublic void run() {System.out.println("线程"+Thread.currentThread().getId()+"执行任务");}});}ex.shutdown();}
}

优缺点:高效管理线程,减少资源消耗;适合大量并发任务场景(如服务器开发)。需要手动管理线程池的关闭,否则可能导致程序无法退出。

线程池

线程池的原理

  线程池是一种池化技术,用于预先创建并且管一组线程,避免频繁的创建和销毁线程的开销,提高性能和相应速度。他几个关键的配置有:核心线程数,最大线程数,空闲存活时间,工作队列,拒绝策略。

通过看线程池的各个重要参数,以及源码中代表的含义:

    public ThreadPoolExecutor(int corePoolSize, //这是核心线程数,一直保持N个线程int maximumPoolSize,//最大线程数,最多创建N个线程long keepAliveTime, //非核心线程空闲N秒后销毁。TimeUnit unit, //时间单位(秒,分,时,日等)BlockingQueue<Runnable> workQueue // 任务队列,最多缓存N个任务) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}

主要的工作原理如下:

1.在默认情况下,线程不会预先创建,一般任务提交的时候才会创建,但是可以通过设置prestartAllCoreThreads可以预先创建核心线程。

2.创建线程,在当前的线程数小于核心线程的情况下,会继续使用核心线程来执行任务。

3.当核心线程数都在工作是,还有任务提交,就会进入,任务队列中等待。

4.现在如果任务队列满了同时核心线程数都在工作,就会创建非核心线程,来安排任务工作,直到创建到的线程数量到达最多线程数量。

5.当任务队列满了,线程数量也到达了最大的线程数量之后,就会产生拒绝策略。

6.如果任务稀少,和线程的空闲时间到达存活时间,那么线程就会销毁,直到线程数到达核心线程数。

 在面试鸭看到一个有趣的解释:

线程池可以理解为,去银行办理业务:默认有6个柜台,当没有人去银行办理业务时候,柜台小姐姐都是拉呱玩耍的,有人去办理业务,先开三个柜台来办理业务,若三个柜台都有人在办理业务,那来的先去等候区,若此时等候区也满了,就去新开另外三个柜台,随着办理业务人员增多,新开的柜台也都有人在办理业务,排队等候区也满了,那你还要来办理业务??想搞事呢??明天再来,或者去前面找个人插队,把第一个等候区的人赶走……,这就 是经理的拒绝策略。

工作队列的不同类型

SysnchronousQusus:不存储任务,直将任务提交给任务。

LinkedBlockingQueue :链表结构的阻塞队列,大小无限。

ArrayBlockingQueue:数组结构的有界阻塞队列。

PriorityBlockingQueue:优先级的误解阻塞队列。

不同线程池的类型

java 并发库中提供了5种常见的线程池实现,只要通过executors工具来创建。

(1)FixedThreadPool: 创建一个固定熟练过的线程池。线程池中的线程数量是固定的,空闲的线程会被复用,线程都在忙则会将新任务放入队列中等待。是适合负载稳定的场景,任务数量确定,且不需要动态调整线程数。

(2)CachedThreadPool:一个可以根据需要创建线程的线程池。线程数的线程没有上线,空闲线程会在60秒后被回收,如果有新任务没有可用线程,会创建新线程,适合短期大量并发任务场景任务执行时间短且线程需求变化较大。

(3)SingleThreadExecutor:创建一个只有单线程的线程池。只有一个线程处理任务,任务会按照提交的顺序执行,适用于需要保证任务按顺序执行的场景,或者不需要处理并发任务的情况。

(4)ScheduledThreadPool:支持定时任务和周期性任务的线程池,可以定时或以固定的频率执行任务。线程池大小也可以有用户指定,使用与需求周期性任务执行的场景,如定时任务的调度。

(5)WorkStealingPool:基于任务窃取算法的线程池。线程池中的每个线程维护一个双端队列,线程可以从自己的队列中取任务执行,如果线程的任务队列为空,他可以从其他队列中拿到任务来执行,形成负载均衡的效果。适合大量小型任务并执行,特别是递归算法或大人物分解成小任务的场景。

4种拒绝策略

1)AbortPolicy,当任务队列满且没有线程空闲时,此时添加任务会直接抛出 RejectedExecutionException 错误,这也是默认的拒绝策略。适用于必须通知调用者任务未能被执行的场景。

2)CallerRunsPolicy,当任务队列满且没有线程空闲时,此时添加的任务由即刻调用者线程执行。适用于希望通过减缓任务提交速度来稳定系统的场景。

3)DiscardOldestPolicy,当任务队列满且没有线程空闲时,会删除最早的任务,然后重新提交当前任务。适用于希望丢弃最旧的任务以保证新的重要任务能够被处理的场景。

4)DiscardPolicy,直接丢弃当前提交的任务,不会执行任何操作,也不会抛出异常。适用于对部分任务丢弃没有影响的场景,或系统负载较高时不需要处理所有任务。

这四种拒绝策略是 Java 线程池(ThreadPoolExecutor)中 RejectedExecutionHandler 接口的实现方式,分别对应不同的业务需求:

策略特点使用场景
AbortPolicy抛异常,强制反馈需要立即感知任务失败
CallerRunsPolicy调用线程自己执行控制任务提交速率,避免雪崩
DiscardOldestPolicy丢弃最老任务优先保障新任务处理
DiscardPolicy无声丢弃可容忍任务丢失,如日志、监控等

为什么线程池要先使用阻塞队列,而不是直接增加线程?

因为每创建一个线程都会占用一定的系统资源(如栈空间、线程调度开销等),直接增加线程会迅速消耗系统资源,导致性能下降。

使用阻塞队列可以将任务暂存,避免线程数量无限增长,确保资源利用率更高。

如果阻塞队列都满了,说明此时系统负载很大,再去增加线程到最大线程数去消化任务即可。

举个例子:老板现在手下有 10 个人在干活(核心线程数),突然活变多了,每个人干不过来了,此时老板不会立马招人,它会让这些活积累一下(放到阻塞队列中),看看过段时间能不能消化掉。如果老板发现这个活积累的实在太多了(队列满了),他才会继续招人(达到最大线程数)。这就是所谓的人员(线程)有成本。

这些大概就是这一篇的全部内容,不想再一篇种写太长,太多的内容所有就大致再这里结尾,但是之后肯定会有其他的相关多线程和线程池的内容,写出来的,敬请期待!!

http://www.dtcms.com/a/614317.html

相关文章:

  • 专业的传媒行业网站开发做医疗网站颜色选择
  • 网站免费搭建平台中山企业网站制作公司
  • 网络:4.1加餐 - 进程间关系与守护进程
  • 边缘算力:云边协同的未来引擎
  • 鸿蒙手机上有没有轻便好用的备忘录APP?
  • Vue3+Vite+Pinia+TS,高效搭建饿了么外卖项目实战教程
  • 成都 网站建设 公司哪家好前端个人介绍网站模板下载
  • 为什么建设长虹网站python流星雨特效代码
  • GTask异步操作管理与使用指南
  • 重庆网站设计制造厂家wordpress文章分页链接优化
  • 【办公类-89-02】20251115优化“课题阶段资料模版“批量制作“6个课题档案袋”插入证书和申请书
  • jsp做网站都可以做什么百度推广必须做手机网站吗
  • 初学C语言使用哪款编译器最好 | 入门学习指南
  • 软件: Keil esp固件烧写软件 华为云服务器(个人免费使用,每天消息上限) 二、调试过程 调试总体思路: 烧写官方的MQTT固 ...
  • C#31、接口和抽象类的区别是什么
  • 网站菜单效果北京市城乡住房建设部网站
  • C++中的公有继承,保护继承和私有继承说明
  • c mvc网站开发在线平面图设计
  • 幻灯片在什么网站做杭州互联网大厂
  • 张懿暄出席中美电影节尽显东方魅力,Mrs Chen角色引期待
  • LeetCode 425 - 单词方块
  • 我要建设一个网站全国可信网站
  • Matlab速成笔记68:质数、质因数分解、阶乘、最大公约数、最小公倍数
  • [智能体设计模式] 第13章:人类参与环节(HITL)
  • 线代强化NO7|秩|矩阵的秩|向量组的秩|极大线性无关组|公式
  • 计算机网络安全--第三章-网络安全体系及管理
  • 11.15 脚本算法 加密网页
  • 前端CSS架构模式,BEM与ITCSS
  • 【深度学习】深度学习概念
  • 大连建设执业资格注册中心网站互联网项目推广