大数据处理系列之(一)Java线程池使用

Java线程池实战:实现一个可暂停、可恢复的线程池 本文的目的是帮助大家理解Java线程池的工作原理,并在此基础上实现一个具有暂停和恢复功能的线程池。我们将涵盖从线程池核心概念的讲解,到具体代码实现的全过程,以及对该线程池在实际应用中的探讨。本文首先会介绍线程池相关的核心概念和它们之间的联系,接着详细讲解实现可暂停、可恢复线程池的算法原理和具体操作步骤,还会给出数学模型和公式进行辅助说明。然后通过项目实战,展示代码的实际案例并进行详细解释。之后探讨该线程池的实际应用场景,推荐相关的工具和资源,分析未来发展趋势与挑战。 阅读详情

前言:最近在做分布式海量数据处理项目,使用到了java的线程池,所以搜集了一些资料对它的使用做了一下总结和探究,

前面介绍的东西大多都是从网上搜集整理而来。文中最核心的东西在于后面两节无界队列线程池和有界队列线程池的实例

使用以及线上问题处理方案。                                

 

1.  为什么要用线程池?

      在Java中,如果每当一个请求到达就创建一个新线程,开销是相当大的。在实际使用中,每个请求创建新线程的服务器

在创建和销毁线程上花费的时间和消耗的系统资源,甚至可能要比花在实际处理实际的用户请求的时间和资源要多的多。除

了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个JVM中创建太多的线程,可能会导致系统由于

过度消耗内存或者“切换过度”而导致系统资源不足。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻

处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象

来进行服务,这就是“池化资源”技术产生的原因。

     线程池主要用来解决线程生命周期开销问题和资源不足问题,通过对多个任务重用线程,线程创建的开销被分摊到多个任

务上了,而且由于在请求到达时线程已经存在,所以消除了创建所带来的延迟。这样,就可以立即请求服务,使应用程序响

应更快。另外,通过适当的调整线程池中的线程数据可以防止出现资源不足的情况。

      网上找来的这段话,清晰的描述了为什么要使用线程池,使用线程池有哪些好处。工程项目中使用线程池的场景比比皆是。

本文关注的重点是如何在实战中来使用好线程池这一技术,来满足海量数据大并发用户请求的场景。

 

2. ThreadPoolExecutor类

       Java中的线程池技术主要用的是ThreadPoolExecutor 这个类。先来看这个类的构造函数,

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,

BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 

    corePoolSize       线程池维护线程的最少数量

    maximumPoolSize    线程池维护线程的最大数量 

    keepAliveTime      线程池维护线程所允许的空闲时间  

    workQueue          任务队列,用来存放我们所定义的任务处理线程

    threadFactory      线程创建工厂

    handler            线程池对拒绝任务的处理策略

     ThreadPoolExecutor 将根据 corePoolSize和 maximumPoolSize 设置的边界自动调整池大小。当新任务在方法

execute(Runnable) 中提交时, 如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是

空闲的。如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。 如果设置的

corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。

     ThreadPoolExecutor是Executors类的实现,Executors类里面提供了一些静态工厂,生成一些常用的线程池,主

要有以下几个:

     newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行

所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任

务的提交顺序执行。  

     newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线

程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

     newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分

空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池

大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

      在实际的项目中,我们会使用得到比较多的是newFixedThreadPool,创建固定大小的线程池,但是这个方法在真实的线上

环境中还是会有很多问题,这个将会在下面一节中详细讲到。

      当任务源源不断的过来,而我们的系统又处理不过来的时候,我们要采取的策略是拒绝服务。RejectedExecutionHandler接

口提供了拒绝任务处理的自定义方法的机会。在ThreadPoolExecutor中已经包含四种处理策略。

      1)CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。

          public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

             if (!e.isShutdown()) {

                 r.run();

            }

        }

这个策略显然不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。

     2)AbortPolicy:处理程序遭到拒绝将抛出运行时 RejectedExecutionException

         public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

              throw new RejectedExecutionException();

        }

 这种策略直接抛出异常,丢弃任务。

      3)DiscardPolicy:不能执行的任务将被删除

          public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}

   这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常。

     4)DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,

则重复此过程)

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

            if (!e.isShutdown()) {

                e.getQueue().poll();

                e.execute(r);

            }

        }

      该策略就稍微复杂一些,在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务。这个策略

需要适当小心。

 

3.  ThreadPoolExecutor无界队列使用

   public class ThreadPool {

        private final static String poolName = "mypool";

        static private ThreadPool threadFixedPool = new ThreadPool(2);

       private ExecutorService executor;

      static public ThreadPool getFixedInstance() {

           return threadFixedPool;

       }

    private ThreadPool(int num) {

           executor = Executors.newFixedThreadPool(num, new DaemonThreadFactory(poolName));

}

public void execute(Runnable r) {

           executor.execute(r);

}

public static void main(String[] params) {

           class MyRunnable implements Runnable {

                    public void run() {

                             System.out.println("OK!");

                             try {

                                       Thread.sleep(10);

                             } catch (InterruptedException e) {

                                       e.printStackTrace();

                             }

                    }

           }

           for (int i = 0; i < 10; i++) {

             ThreadPool.getFixedInstance().execute(new MyRunnable());

           }

           try {

                    Thread.sleep(2000);

                    System.out.println("Process end.");

           } catch (InterruptedException e) {

                    e.printStackTrace();

           }

}

}

       在这段代码中,我们发现我们用到了Executors.newFixedThreadPool()函数,这个函数的实现是这样子的:

return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()); 

       它实际上是创建了一个无界队列的固定大小的线程池。执行这段代码,我们发现所有的任务都正常处理了。但是在真实的线上环

境中会存在这样的一个问题,前端的用户请求源源不断的过来,后端的处理线程如果处理时间变长,无法快速的将用户请求处理

完返回结果给前端,那么任务队列中将堵塞大量的请求。这些请求在前端都是有超时时间设置的,假设请求是通过套接字过来,

当我们的后端处理进程处理完一个请求后,从队列中拿下一个任务,发现这个任务的套接字已经无效了,这是因为在用户端已经

超时,将套接字建立的连接关闭了。这样一来我们这边的处理程序再去读取套接字时,就会发生I/0 Exception. 恶性循环,导致我

们所有的处理服务线程读的都是超时的套接字,所有的请求过来都抛I/O异常,这样等于我们整个系统都挂掉了,已经无法对外提供

正常的服务了。

     对于海量数据的处理,现在业界都是采用集群系统来进行处理,当请求的数量不断加大的时候,我们可以通过增加处理节点,反正现

在硬件设备相对便宜。但是要保证系统的可靠性和稳定性,在程序方面我们还是可以进一步的优化的,我们下一节要讲述的就是针对

线上出现的这类问题的一种处理策略。

 

4.   ThreadPoolExecutor有界队列使用

public class ThreadPool {

         private final static String poolName = "mypool";

         static private ThreadPool threadFixedPool = null;

         public ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(2);

         private ExecutorService executor;

 

         static public ThreadPool getFixedInstance() {

                   return threadFixedPool;

         }

         private ThreadPool(int num) {

                   executor = new ThreadPoolExecutor(2, 4,60,TimeUnit.SECONDS, queue,new DaemonThreadFactory

(poolName), new ThreadPoolExecutor.AbortPolicy());

         }

         public void execute(Runnable r) {

                   executor.execute(r);

         }

        

         public static void main(String[] params) {

                   class MyRunnable implements Runnable {

                            public void run() {

                                     System.out.println("OK!");

                                     try {

                                               Thread.sleep(10);

                                     } catch (InterruptedException e) {

                                               e.printStackTrace();

                                     }

                            }

                   }

                   int count = 0;

                   for (int i = 0; i < 10; i++) {

                            try {

                                     ThreadPool.getFixedInstance().execute(new MyRunnable());

                            } catch (RejectedExecutionException e) {

                                     e.printStackTrace();

                                     count++;

                            }

                   }

                   try {

                            log.info("queue size:" + ThreadPool.getFixedInstance().queue.size());

                            Thread.sleep(2000);

                   } catch (InterruptedException e) {

                            e.printStackTrace();

                   }

                   System.out.println("Reject task: " + count);

         }

}

       首先我们来看下这段代码几个重要的参数,corePoolSize 为2,maximumPoolSize为4,任务队列大小为2,每个任务平

均处理时间为10ms,一共有10个并发任务。

      执行这段代码,我们会发现,有4个任务失败了。这里就验证了我们在上面提到有界队列时候线程池的执行顺序。当新任务在

方法 execute(Runnable) 中提交时, 如果运行的线程少于 corePoolSize,则创建新线程来处理请求。 如果运行的线程多于

corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程,如果此时线程数量达到maximumPoolSize,并且队

列已经满,就会拒绝继续进来的请求。

    现在我们调整一下代码中的几个参数,将并发任务数改为200,执行结果Reject task: 182,说明有18个任务成功了,线程处理

完一个请求后会接着去处理下一个过来的请求。在真实的线上环境中,会源源不断的有新的请求过来,当前的被拒绝了,但只要线

程池线程把当下的任务处理完之后还是可以处理下一个发送过来的请求。

     通过有界队列可以实现系统的过载保护,在高压的情况下,我们的系统处理能力不会变为0,还能正常对外进行服务,虽然有些服

务可能会被拒绝,至于如何减少被拒绝的数量以及对拒绝的请求采取何种处理策略我将会在下一篇文章《系统的过载保护》中继续

阐述。

 

参考文献:

  1. ThreadPoolExecutor使用与思考(上)-线程池大小设置与BlockedQueue的三种实现区别 http://dongxuan.iteye.com/blog/901689
  2. ThreadPoolExecutor使用与思考(中)-keepAliveTime及拒绝策略http://dongxuan.iteye.com/blog/902571
  3. ThreadPoolExecutor源代码
  4. Java线程池介绍以及简单实例 http://wenku.baidu.com/view/e4543a7a5acfa1c7aa00cc25.html
Elasticsearch 线程池及相关配置 - 大数据处理 在线程池的配置中,合理地设置线程池的大小和队列大小可以提高 Elasticsearch 的性能和吞吐量。通过适当的线程池配置,我们可以优化不同类型的请求处理,从而更好地满足大数据处理的需求。在实际应用中,根据具体的业务场景和系统负载,可以进一步调整线程池的配置参数,以实现最以进一步调整线程池的配置参数,以实现最佳性能。在 Elasticsearch 中,线程池是一个关键的组件,它负责管理并发请求的处理,以提高系统性能和吞吐量。请注意,这只是一个简单的示例,实际上可以根据需求进行更复杂的线程池配置和操作。 阅读详情

相关推荐

线程池的封装与大数据处理

通过合理地使用线程池,我们可以充分利用多核处理器和多线程技术,提高大数据处理的效率和性能。在大数据处理中,线程池的封装可以帮助我们更好地管理和利用系统资源,提高数据处理的速度和并发性。在大数据处理中,我们可以将数据划分成多个子任务,然后使用线程池同时处理多个子任务,以提高处理速度。在大数据处理中,线程池是一种常见且重要的技术,用于管理和调度线程的执行。本文将介绍如何封装线程池,并结合大数据处理的场景,提供相应的源代码。通过构造函数指定线程池的大小,这里我们使用了固定大小的线程池。线程池的封装与大数据处理。

PixelInk的博客 134

线程池的5种使用方式

池化思想,不同线程池的创建方式以及差别,线程数设置的经验

a141210104的专栏 6137

Java线程池的优化策略与最佳实践

在上一期中,我们深入探讨了Java线程池的使用,包括接口的核心功能及其应用场景。然而,仅仅掌握线程池的基本用法还不够,在实际开发中,合理优化线程池的配置和使用策略是确保多线程程序高效运行的关键。本期内容将深入探讨Java线程池的优化策略与最佳实践,帮助你提升多线程编程的性能和稳定性。本文将介绍Java线程池的常见优化策略,包括线程池参数的调整、任务队列的选择、拒绝策略的配置以及监控线程池的状态等。

喵手的博客 1580

线程池的使用

java中经常需要用到多线程来处理一些业务,我们非常不建议单纯使用继承Thread或者实现Runnable接口的方式来创建线程,那样势必有创建及销毁线程耗费资源、线程上下文切换问题。同时创建过多的线程也可能引发资源耗尽的风险,这个时候引入线程池比较合理,方便线程任务的管理。java中涉及到线程池的相关类均在jdk1.5开始的java.util.concurrent包中,涉及到的几个核心类及接口包括:Executor、Executors、ExecutorService、ThreadPoolExecutor、F

xiaowang_lj的博客 6099

Java大数据处理示例教程:代码-例3-4

Java集合框架(Java Collections Framework)提供了用于存储和操作数据的接口和类。它是大数据处理不可或缺的一部分,因为大数据本质上是数据的集合,需要有效的方式来管理数据的集合。集合框架包括了List、Set、Queue和Map等数据结构,每个结构在处理不同类型的数据时都有着其独特的优势。例如,List提供了有序的数据集合,适合需要顺序访问元素的场景;而Set提供了唯一性的约束,适合去除重复元素的场景。

weixin_28850145的博客 852

在Docker中集成GPU监控体系

先上效果图 等文档整理出来再更

tamako0v0的博客 2470

常用线程池介绍

线程池简介 1.线程池概念: 线程池是首先创建一个线程,它们的集合成为线程池。使用线程池可以很好的提升性能,线程池在启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行线程完成后,该线程不会死亡,而是返回线程池中成为空闲状态,等待执行下一个任务。 2.线程池的工作机制 2.1在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。 2.2一个线程同时只能执行一个任

NewBie_xW的博客 1055

告别NEDC!手把手教你将CLTC/WLTP等最新工况文件导入AVL Cruise(附资源包)

本文详细指导如何将CLTC、WLTP等最新工况文件导入AVL Cruise软件,替代传统的NEDC工况。从资源获取、文件预处理到分步导入指南,再到常见问题排查,帮助工程师高效完成汽车仿真性能评估。附赠全球主流工况文件资源包,助力精准仿真分析。

weixin_30374009的博客 461

线程池的使用(7种创建方法)

线程池的7种创建方法

m0_48273471的博客 3万+

别再手动抄表了!用PaddleOCR超轻量模型5分钟搞定数字仪表识别(附完整Python代码)

本文介绍如何利用PaddleOCR超轻量模型快速实现工业仪表数字识别自动化。通过5行核心代码和关键预处理步骤,显著提升识别准确率至98.2%,解决传统OCR在工业场景中的体积大、抗干扰弱等问题,并提供完整Python代码和边缘部署优化方案。

weixin_31062533的博客 347

投研必备工具:Wind(万得)数据库快速入门

Wind数据库对于金融从业者来说是必不可少的一个软件,本文主要介绍了股票和债券的信息查找,帮助后续对数据进行分析,开始学习吧!

weixin_56581108的博客 10万+

java大数据处理系列之(一)Java线程池使用

前言:最近在做分布式海量数据处理项目,使用到了java的线程池,所以搜集了一些资料对它的使用做了一下总结和探究, 前面介绍的东西大多都是从网上搜集整理而来。文中最核心的东西在于后面两节无界队列线程池和有界队列线程池的实例 使用以及线上问题处理方案。 1. 为什么要用线程池? 在Java中,如果每当一个请求到...

Jumbro的博客 506

拯救者R7000P(2021)装双系统Ubuntu20.04 解决联发科MT7921无线网卡在Ubuntu中不能联网

拯救者R7000P(2021)装双系统Ubuntu20.04/Ubuntu18.04 电脑配置:联想拯救者R7000P CPU:AMD R7-5800H GPU:RTX3060-6G 内存:16GB 固态:500G(原装)+500(加装) 1.做系统盘(本文以Ubuntu20.04为例) 1.下载Ubuntu20.04镜像 直接去Ubuntu官网或者一些国内的镜像去下载都可以,这里给出一个网址https://ubuntu.com/download/desktop,根据需要下载自己学要的Ubuntu版本,后缀

qq_41108639的博客 1万+

未整理--大数据处理系列之(一)Java线程池使用, Java 使用线程池执行大数据量统计任务

https://www.cnblogs.com/cstar/archive/2012/06/14/2549494.html https://blog.youkuaiyun.com/difffate/article/details/77149901 https://blog.youkuaiyun.com/qiyongkang520/article/details/47904863 https://blog.csdn....

try to do 819

C#winform自定义绘制曲线控件实例

本文实例讲解自定义绘制曲线控件过程。 在实际项目中,需要绘制曲线,系统的提供的绘制曲线的控件有时不能满足自己的需要,需要根据项目需要定制开发曲线控件。 实例目标,实时绘制显示温湿度值曲线。

qq_30725967的博客 1734

FLUENT Meshing Watertight Geometry工作流入门 - 4 局部加密区域

总结一下本课程中学到的内容:我们了解了如何使用模型中特定兴趣区域的BOI和FOI局部尺寸控制来加密网格。然后,了解了如何使用Box类型的局部细化区域。最后,学习了Ansys Fluent Meshing Watertight Geometry Workflow的Offset Surface类型创建局部细化区域任务。

B_etter_Man的博客 6640

【Java 线程池】详解

线程池在不同类型的项目中都有着广泛且关键的应用,通过合理地运用线程池机制,根据具体业务场景和需求精细配置其各项参数,能够充分发挥多核处理器的优势,高效管理线程资源,提升系统的并发处理能力、响应速度以及稳定性,为各类软件应用的高质量运行提供坚实的保障,满足不同用户群体的使用需求,助力项目在复杂的业务环境和高并发需求下顺利开展。

架构师之路 4099

Yolov8-pose关键点检测:loss优化 | LRM loss困难样本挖掘,提升难样本、遮挡物等检测精度

LRM loss应用到能够大幅提升pose遮挡物性能

①答疑群聊服务;②YOLO大模型知识问答系统;③计算机视觉论文生成智能体; 1713

java大数据处理总结

从总体上来看,对于大型网站,比如门户网站,在面对大量用户访问、高并发请求方面,基本的解决方案集中在这样几个环节: 1.首先需要解决网络带宽和Web请求的高并发,需要合理的加大服务器和带宽的投入,并且需要充分的利用系统中软件、硬件的缓存机制,将能缓存的内容都进行缓存存储,减少计算层和存储层的压力。 2.其次需要对业务服务器和业务支撑服务器进行合理的分层,并且采用并行计算和分布式算法对大量计算进行处理,并且在开发的过程中需要采用JavaSDK中并发包(Concurrency)进行编码实现。 3.存储层需要.

shirdon-廖显东的博客 2613

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Java常用线程池-固定线程池使用

Java常用线程池-固定线程池使用固定线程池 固定线程池 在执行数据处理时不可避免的需要使用多线程进行数据处理,以此提高数据处理效率,降低处理时间。而线城池现在池容器对数据处理效率提升有举足轻重的作用,一般我们会在实际应用中使用固定线程池。 package com.base.service; import com.base.service.common.loghub.LogHubAdapter;...

xuxin132133的博客 3336

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Java[8]:线程池

(1) 线程池是什么线程池是一种高效的线程管理机制,它通过预先创建并维护一组可复用的线程,显著减少了频繁创建和销毁线程的系统开销,从而优化了并发任务的执行效率,提升了整体系统性能。它的主要功能如下。

正直朱的博客 1892

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

上一篇: java类过滤器,防止页面sql注入
下一篇: JProfiler监控内存
andyLiuJava
博客等级 码龄14年 0粉丝 31原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值