`
liuzhaomin
  • 浏览: 207576 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

线程池的介绍及简单实现--ibmdw

阅读更多

线程池的技术背景

在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术产生的原因。比如大家所熟悉的数据库连接池正是遵循这一思想而产生的,本文将介绍的线程池技术同样符合这一思想。

目前,一些著名的大公司都特别看好这项技术,并早已经在他们的产品中应用该技术。比如IBM的WebSphere,IONA的Orbix 2000在SUN的 Jini中,Microsoft的MTS(Microsoft Transaction Server 2.0),COM+等。

现在您是否也想在服务器程序应用该项技术?

线程池技术如何提高服务器程序的性能

我所提到服务器程序是指能够接受客户请求并能处理请求的程序,而不只是指那些接受网络客户请求的网络服务器程序。

多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。但如果对多线程应用不当,会增加对单个任务的处理时间。可以举一个简单的例子:

假设在一台服务器完成一项任务的时间为T

     T1 创建线程的时间
      T2 在线程中执行任务的时间,包括线程间同步所需时间
      T3 线程销毁的时间		

显然T = T1+T2+T3。注意这是一个极度简化的假设。

可以看出T1,T3是多线程本身的带来的开销,我们渴望减少T1,T3所用的时间,从而减少T的时间。但一些线程的使用者并没有注意到这一点,所以在程序中频繁的创建或销毁线程,这导致T1和T3在T中占有相当比例。显然这是突出了线程的弱点(T1,T3),而不是优点(并发性)。

线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。

线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目。在看一个例子:

假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。我们比较利用线程池技术和不利于线程池技术的服务器处理这些请求时所产生的线程总数。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目或者上限(以下简称线程池尺寸),而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池尺寸是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。

这些都是假设,不能充分说明问题,下面我将讨论线程池的简单实现并对该程序进行对比测试,以说明线程技术优点及应用领域。

线程池的简单实现及对比测试

一般一个简单线程池至少包含下列组成部分。

  1. 线程池管理器(ThreadPoolManager):用于创建并管理线程池
  2. 工作线程(WorkThread): 线程池中线程
  3. 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。
  4. 任务队列:用于存放没有处理的任务。提供一种缓冲机制。

线程池管理器至少有下列功能:创建线程池,销毁线程池,添加新任务 创建线程池的部分代码如下:

  …
        //create threads
        synchronized(workThreadVector)
        {
            for(int j = 0; j < i; j++)
            {
                threadNum++;
               WorkThread workThread = new WorkThread(taskVector, threadNum);
                workThreadVector.addElement(workThread);
            }
        }
…

 

注意同步workThreadVector并没有降低效率,相反提高了效率,请参考Brian Goetz的文章。 销毁线程池的部分代码如下:

  …
        while(!workThreadVector.isEmpty())
        {
        if(debugLevel > 2)
         System.out.println("stop:"+(i));
         i++;
            try
            {
                WorkThread workThread = (WorkThread)workThreadVector.remove(0);
                workThread.closeThread();
                continue;
            }
            catch(Exception exception)
            {
                if(debugLevel > 2)
                    exception.printStackTrace();
            }
            break;
        }
   …
   

 

添加新任务的部分代码如下:

   …
        synchronized(taskVector)
        {
            taskVector.addElement(taskObj);
            taskVector.notifyAll();
        }
   …
   

 

工作线程是一个可以循环执行任务的线程,在没有任务时将等待。由于代码比较多在此不罗列.

任务接口是为所有任务提供统一的接口,以便工作线程处理。任务接口主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等。在文章结尾有相关代码的下载。

以上所描述的线程池结构很简单,一些复杂的线程池结构将不再此讨论。

在下载代码中有测试驱动程序(TestThreadPool),我利用这个测试程序的输出数据统计出下列测试结果。测试有两个参数要设置:

  1. 线程池中线程数,即线程池尺寸。
  2. 要完成的任务数。

分别将一个参数固定,另一个参数变动以考察两个参数所产生的不同结果。所用测试机器分别为普通PC机(Win2000 JDK1.3.1)和SUN服务器(Solaris Unix JDK1.3.1),机器配置在此不便指明。


表1:测试数据及对应结果

线程池尺寸 任务数 没有应用线程池所用的时间(单位:毫秒,OS:win) 应用线程池所用的时间(单位:毫秒,OS:win) 没有应用线程池所用的时间(单位:毫秒,OS:Solaris) 应用线程池所用的时间(单位:毫秒,OS:Solaris)
1 5000 3896 130 6513 327
2 5000 3455 151 6221 659
4 5000 3425 120 5448 433
8 5000 3475 160 5769 1478
16 5000 3505 211 5785 1970
32 5000 3455 251 6403 875
64 5000 3595 501 5182 1103
128 5000 3515 881 5154 405
256 5000 3495 3104 5502 1589
512 5000 3425 5488 5667 1262
16 1 20 0 22 3
16 2 20 20 21 13
16 4 20 10 27 10
16 8 20 20 22 24
16 16 30 20 29 48
16 32 40 20 46 108
16 64 60 20 72 199
16 128 110 20 148 335
16 256 201 20 252 132
16 512 411 40 522 382
16 1024 811 71 1233 610
16 2048 1552 80 2045 135
16 4096 2874 250 4828 787


图1.线程池的尺寸的对服务器程序的性能影响
图1.线程池的尺寸的对服务器程序的性能影响

根据以上统计数据可得出下图:


图2.任务数对服务器程序的冲击 
图2.任务数对服务器程序的冲击

数据分析如下:

图1是改变线程池尺寸对服务器性能的影响,在该测试过程中,服务器的要完成的任务数固定为为5000。从图1中可以看出合理配置线程池尺寸对于大量任务处理的效率有非常明显的提高,但是一旦尺寸选择不合理(过大或过小)就会严重降低影响服务器性能。理论上"过小"将出现任务不能及时处理的情况,但在图表中显示出某些小尺寸的线程池表现很好,这是因为测试驱动中有很多线程同步开销,且这个开销相对于完成单个任务的时间是不能忽略的。"过大"则会出现线程间同步开销太大的问题,而且在线程间切换很耗CPU时间,在图表显示的很清楚。可见任何一个好技术,如果滥用都会造成灾难性后果。

图2是用不同数量的任务来冲击服务器程序,在该测试过程中,服务器线程池尺寸固定为16。可以看出线程池在处理少量任务时的优势不明显。所以线程池技术有一定的适应范围,关于适用范围将在后面讨论。但对于大量的任务的处理,线程池的优势表现非常卓越,服务器程序处理请求的时间虽然有波动,但是其平均值相对小多了。

值得注意的是测试方案中,统计任务的完成时间没有包含了创建线程池的时间。在实际线程池工作时,即利用线程池处理任务时,创建线程池的时间是不必计算在内的。

由于测试驱动程序有很多同步代码,特别是等待线程执行完毕的同步(代码中为sleepToWait(long l)方法的调用),这些代码降低了代码执行效率,这是测试驱动一个缺点,但这个测试驱动可以说明线程池相对于简单使用线程的优势。

关于高级线程池的探讨

简单线程池存在一些问题,比如如果有大量的客户要求服务器为其服务,但由于线程池的工作线程是有限的,服务器只能为部分客户服务,其它客户提交的任务,只能在任务队列中等待处理。一些系统设计人员可能会不满这种状况,因为他们对服务器程序的响应时间要求比较严格,所以在系统设计时可能会怀疑线程池技术的可行性,但是线程池有相应的解决方案。调整优化线程池尺寸是高级线程池要解决的一个问题。主要有下列解决方案:

方案一:动态增加工作线程

在一些高级线程池中一般提供一个可以动态改变的工作线程数目的功能,以适应突发性的请求。一旦请求变少了将逐步减少线程池中工作线程的数目。当然线程增加可以采用一种超前方式,即批量增加一批工作线程,而不是来一个请求才建立创建一个线程。批量创建是更加有效的方式。该方案还有应该限制线程池中工作线程数目的上限和下限。否则这种灵活的方式也就变成一种错误的方式或者灾难,因为频繁的创建线程或者短时间内产生大量的线程将会背离使用线程池原始初衷--减少创建线程的次数。

举例:Jini中的TaskManager,就是一个精巧线程池管理器,它是动态增加工作线程的。SQL Server采用单进程(Single Process)多线程(Multi-Thread)的系统结构,1024个数量的线程池,动态线程分配,理论上限32767。

方案二:优化工作线程数目

如果不想在线程池应用复杂的策略来保证工作线程数满足应用的要求,你就要根据统计学的原理来统计客户的请求数目,比如高峰时段平均一秒钟内有多少任务要求处理,并根据系统的承受能力及客户的忍受能力来平衡估计一个合理的线程池尺寸。线程池的尺寸确实很难确定,所以有时干脆用经验值。

举例:在MTS中线程池的尺寸固定为100。

方案三:一个服务器提供多个线程池

在一些复杂的系统结构会采用这个方案。这样可以根据不同任务或者任务优先级来采用不同线程池处理。

举例:COM+用到了多个线程池。

这三种方案各有优缺点。在不同应用中可能采用不同的方案或者干脆组合这三种方案来解决实际问题。

线程池技术适用范围及应注意的问题

下面是我总结的一些线程池应用范围,可能是不全面的。

线程池的应用范围:

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
  2. 对性能要求苛刻的应用,比如要求服务器迅速相应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,并出现"OutOfMemory"的错误。

结束语

本文只是简单介绍线程池技术。可以看出线程池技术对于服务器程序的性能改善是显著的。线程池技术在服务器领域有着广泛的应用前景。希望这项技术能够应用到您的多线程服务程序中。

 

参考资料

Danny Ayers 等:Professional Java Server Programming

Tarak Modi: Why Thread Pools are Important in Java

Brett Spell :Professional java Programming

Jini(TM) v1.2.1代码

Brian Goetz 的 Java 理论和实践专栏

Microsoft Knowledge Base Article :Q282490

关于作者

幸勇 一位软件设计开发者。爱好广泛。

 

分享到:
评论

相关推荐

    JAVA线程、线程池资料----下载不扣分,回帖加1分,欢迎下载,童叟无欺

    .......................................JAVA线程、线程池资料----下载不扣分,回帖加1分,欢迎下载,童叟无欺JAVA线程、线程池资料----下载不扣分,回帖加1分,欢迎下载,童叟无欺JAVA线程、线程池资料----下载不...

    C#线程池的实现---thrend

    根据给定的信息,本文将详细解析C#中的线程池实现方法,并重点介绍如何通过Mutex与Interlocked等机制来管理线程同步。 ### C#线程池的基本概念 线程池是C#中用于管理线程的一种高效机制,它允许程序在运行时动态地...

    线程池的概念及实践---word文档

    在.NET框架中,`System.Threading.ThreadPool`类提供了线程池的实现。开发者可以使用`ThreadPool.QueueUserWorkItem`方法来提交任务,该方法接受一个`WaitCallback`类型的委托,表示要执行的方法,以及一个可选的`...

    线程-线程池-锁-集合-Map-队列.docx

    `HashMap`在不同JDK版本间可能存在差异,例如在JDK 7及之前,`HashMap`是非线程安全的,而在JDK 8中进行了优化,引入了红黑树提高性能。`ConcurrentHashMap`是线程安全的Map实现,适用于多线程环境下的并发访问。 ...

    线程池与工作队列--Java 理论与实践

    #### 一、线程池的概念及重要性 线程池是一种广泛应用于服务器应用程序的技术,主要用于优化资源利用效率,提高程序响应速度,并确保系统的稳定性和可靠性。在多线程编程环境中,线程池通过复用一组预先创建好的...

    Windows(VC doc)下C语言线程池聊天室-服务器-客户端

    在Windows环境下,使用Visual C++ 6.0(简称VC6)进行C语言编程时,可以构建线程池聊天室项目。这个项目涉及到的核心技术包括C语言编程、多线程处理、Socket套接字编程以及文件操作。接下来,我们将详细讨论这些知识...

    uThreadPool线程池示例(查找0-1亿之间的质数任务)

    本示例中,我们关注的是一个基于`uThreadPool`实现的线程池,它用于执行查找0到1亿之间所有质数的任务。这个任务展示了线程池在处理大量计算密集型工作时的能力。 首先,线程池是由一组工作线程组成的集合,它们...

    Java 线程池的原理与实现

    - **定长线程池**:`Executors.newScheduledThreadPool(int corePoolSize)`,支持定时及周期性任务执行。 4. **线程池参数** - **corePoolSize**:线程池的基本大小,即使无任务执行,也会保持这个数量的线程不被...

    易语言真正的线程池简易实现

    易语言简易线程池的实现。 ——V雪落有声V原创。转载请保留。前文:。为了能充分理解本篇文章的内容,需要了解的知识如下:。1.事件对象的使用:http://baike.baidu.com/view/751499.htm。2.信号量的使用:...

    线程池 线程池的介绍及简单实现

    在本篇文章中,我们将深入探讨线程池的原理、实现方式以及在C++中的扩展应用。 首先,线程池的基本原理是通过维护一组可复用的线程来减少创建和销毁线程的开销。当一个新任务提交到线程池时,如果线程池中有空闲...

    线程池方式的libevent-server.zip

    线程池技术在现代软件开发中扮演着至关重要的角色,特别是在高性能网络服务器的实现中。Libevent是一个事件通知库,它允许程序以高效的方式处理大量的并发连接。在"线程池方式的libevent-server.zip"中,我们看到的...

    linux 实现一个简单的线程池及工作

    本实例将深入探讨如何在Linux下实现一个简单的线程池,并介绍相关的关键知识点。 1. **线程与线程池的概念** - **线程**:是操作系统分配CPU时间片的基本单位,是程序执行的流,一个进程中可以包含多个线程,它们...

    C++线程池c-thread-pool-master.zip

    C++线程池是一种高效的并发执行机制,它允许开发者预先创建一组线程,然后将任务提交到线程池中,由线程池统一管理和调度。这样可以避免频繁地创建和销毁线程,提高系统的响应速度和并发性能。在c-thread-pool-...

    线程池原理及创建(C++实现)

    ### 线程池原理及创建(C++实现) #### 一、线程池的重要性 在现代计算环境中,网络服务器面临着处理大量并发请求的挑战,其中包括但不限于Web服务器、电子邮件服务器和数据库服务器。这类服务器通常需要在短时间...

    day18【线程池、Lambda表达式】-笔记1

    【线程池与Lambda表达式】是Java编程中两个重要的概念。线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。它允许开发者管理线程的生命周期,有效地重用线程,避免因频繁...

    day07【线程池、Lambda表达式】-笔记.pdf

    总结来看,理解线程通信概念和等待唤醒机制对于开发多线程程序至关重要,线程池的使用能有效提升程序的性能和资源利用率,而Lambda表达式的引入则简化了函数式编程的实现。这些知识点对于Java开发者来说是必须掌握的...

    Spring Boot整合FTPClient线程池的实现示例

    Spring Boot 整合 FTPClient 线程池的实现示例 在本文中,我们将探讨如何在 Spring Boot 项目中整合 FTPClient 线程池的实现示例。FTPClient 是一个常用的 FTP 客户端库,而线程池则可以帮助我们减少频繁创建和销毁...

    课程作业-基于C语言实现线程池源码.zip

    课程作业-基于C语言实现线程池源码.zip课程作业-基于C语言实现线程池源码.zip课程作业-基于C语言实现线程池源码.zip课程作业-基于C语言实现线程池源码.zip课程作业-基于C语言实现线程池源码.zip课程作业-基于C语言...

    徒手实现线程池-1

    线程池的实现涉及到多种类型的队列和策略,每种都有其特定的适用场景。下面我们将详细讨论这些知识点。 1. **ExecutorService**: ExecutorService 是 Java.util.concurrent 包中的一个接口,它是 Executor 接口的...

    简单JAVA线程池应用---服务器端

    此文档是: 基于简单线程池概念的JAVA服务器端应用 附有连接ORACLE数据库等简单操作. 操作描述:  服务器启动后,会启动10个子线程运行.(配合客户端10个请求进行模拟,控制台输出模拟过程) 服务器主程序进入一个有...

Global site tag (gtag.js) - Google Analytics