`

多线程(二)

阅读更多

标记一下比较重要的类:
ExecutorService: 真正的线程池接口。
ScheduledExecutorService 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutor ExecutorService的默认实现。
ScheduledThreadPoolExecutor 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。
• newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
• newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
• newCachedThreadPool: 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可 以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
那 我个人感觉就是new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEPALIVE_TIME, TIME_UNIT, workQueue, rejectedExecutionHandler);提供了更定制化的线程池制造方法。因为newFixedThreadPool方法其实也是 return new ThreadPoolExecutor

java.util.concurrent.Executors类的API提供大量创建连接池的静态方法:
1.固定大小的线程池:
package BackStage;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
public class JavaThreadPool {
    public static void main(String[] args) {
// 创建一个可重用固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
// 创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
Thread t4 = new MyThread();
Thread t5 = new MyThread();
// 将线程放入池中进行执行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
// 关闭线程池
pool.shutdown();
    }
}
class MyThread extends Thread {
    @Override
    public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行。。。");
    }
}
2.单任务线程池:
//创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
ExecutorService pool = Executors.newSingleThreadExecutor();
对于以上两种连接池,大小都是固定的,当要加入的池的线程(或者任务)超过池最大尺寸时候,则入此线程池需要排队等待。一旦池中有线程完毕,则排队等待的某个线程会入池执行。
总结: 一.FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
        二.CachedThreadPool的特点就是在线程池空闲时,即线程池中没有可运行任务时,它会释放工作线程,从而释放工作线程所占用的资源。但是,但当出现新任务时,又要创建一新的工作线程,又要一定的系统开销。并且,在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。




一:  应用程序中会出现这种情况:对服务器请求的数目非常大,但是服务器对每个请求处理的时间却很短。如果对这种情况,每个请求都创建一个线程的话,那么创建线程和销毁线程所使用的时间和资源远远大于用户请求的时间和资源,而且创建太多的线程会导致内存过度消耗从而影响性能。
  而线程池很好的解决这个问题,当请求到达时,请求等待一个可用的线程,然后将任务交给线程进行执行,当任务执行完成之后,将线程返还给线程池。java.util.concurrent 是在并发编程中很常用的实用工具包,它包括互斥、信号量、诸如在并发访问下执行得很好的队列和散列表之类集合类以及几个工作队列实现。
二: Executors类中提供了几种创建线程池的方法:
   newFixedThreadPool:创建一个可重用固定线程数的线程池,当任务到来的时候会new一个线程,立马执行;如果所有的线程是活动的,新任务将在队列中等待,直到线程可用。任何线程在执行过程中终止或由于故障关断时,如果需要的话,会以一个新的线程将代替它执行后续的任务。
   newSingleThreadExecutor:创建一个使用单个worker线程操作一个Executor无界队列的线程池。此线程池只有一个线程在工作,如果任务在执行过程中线程终止或故障关机,会有一个新的线程来代替它完成后续任务。此线程池保证所有任务按照提交顺序执行。
   newCachedThreadPool:可根据需要创建新可缓存的线程池,对于执行很多短期异步任务的程序如果有可用线程那将重用以前构造的线程而提高性能,如果没有现有线程可用时,一个新的线程将被创建并添加到池中。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲的线程。
   newScheduledThreadPool:创建一个给定延迟或者定期执行的线程池。
三:通过源码几种线程池都是通过ThreadPoolExecutor构造出来的,下面对ThreadPoolExecutor构造函数的几个参数做简单的描述:
    corePoolSize - 池中所保存的线程数,包括空闲线程。
    maximumPoolSize - 池中允许的最大线程数。
    keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime 参数的时间单位。
workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交 Runnable 任务。

执行的步骤:
   提交一个runnable任务,当poolSize(当前池的大小)<corePoolSize(池中所保存的线程数),会直接做为创建一个线程,立马执行任务。
   当提交的runnable任务数超过了corePoolSize(池中所保存的线程数),会将当前的runable提交到一个块队列中。
   如果块队列是有界队列,当队列满了之后如果poolSize(当前池的大小) < maximumPoolsize(池中允许的最大线程数)时,会尝试创建一个线程来进行救急处理,立马执行对应的runnable任务。
   如果救急方案也无法处理了,就会执行RejectedExecutionHandler操作(进行拒绝、抛弃、抛弃队列最早的、调用运行)。

线程池的技术背景
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在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.线程池的尺寸的对服务器程序的性能影响
根据以上统计数据可得出下图:
图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"的错误。
结束语
本文只是简单介绍线程池技术。可以看出线程池技术对于服务器程序的性能改善是显著的。线程池技术在服务器领域有着广泛的应用前景。希望这项技术能够应用到您的多线程服务程序中。
分享到:
评论

相关推荐

    第二章多任务和多线程 第二章多任务和多线程

    第二章多任务和多线程第二章多任务和多线程第二章多任务和多线程第二章多任务和多线程第二章多任务和多线程第二章多任务和多线程第二章多任务和多线程第二章多任务和多线程第二章多任务和多线程第二章多任务和多线程...

    Revit二次开发 c# 多线程处理

    为了解决这个问题,开发者通常会借助C#语言的多线程技术来提升效率。本文将深入探讨如何在Revit中实现C#的多线程处理,并通过实例和源代码来阐述这一过程。 首先,我们需要理解Revit的API限制。Revit不允许在非主线...

    易语言多线程传递多参数

    在编程领域,多线程是实现并发执行任务的重要机制,特别是在易语言中,它能有效提升程序的执行效率。易语言是一种中文编程语言,旨在降低编程门槛,让普通用户也能进行程序开发。本文将深入探讨易语言中的多线程以及...

    PB多线程实现

    二、PB12.5的多线程支持 PB12.5引入了对多线程的更好支持,它引入了一个名为“Worker Thread”的新概念。开发者可以创建一个工作线程对象,然后在这个对象上执行自定义的代码块。这使得在PB应用中实现多线程变得更加...

    多线程的运用e语言多线程 e多线程

    在编程领域,多线程是一种重要的并发执行机制,它允许程序同时执行多个任务,从而提高系统资源利用率和程序响应速度。E语言(可能是错误输入或者是某个特定编程环境或语言的简称)中的多线程功能也不例外。本文将...

    C#.NET多线程实例6个(包括多线程基本使用,多线程互斥等全部多线程使用实例),可直接运行

    在.NET框架中,C#语言提供了强大的多线程支持,使得开发者可以充分利用现代多核处理器的优势,实现并行处理和高效能编程。本资源包含六个C#.NET多线程的实例,涵盖了多线程的基本使用到更高级的概念,如线程互斥。...

    多线程基础与基于多线程的简单聊天室

    本压缩包“多线程基础与基于多线程的简单聊天室”提供了对多线程技术的实践理解和二次开发的基础。以下是关于这个主题的详细知识点: 1. **多线程的概念**:多线程是指在一个程序中同时执行多个不同的线程,每个...

    c#多线程编程实战(原书第二版)源码

    《C#多线程编程实战(原书第二版)源码》是一本深入探讨C#中多线程技术的专业书籍,其源码提供了丰富的实践示例,帮助读者掌握并发编程的核心概念和技术。在C#中,多线程是实现高性能、响应式应用程序的关键组成部分...

    Delphi多线程详解_delphi_delphi多线程_多线程_

    在编程领域,多线程是一种常见且强大的技术,它允许应用程序同时执行多个任务,从而提高程序的效率和响应性。Delphi,作为一个流行的Object Pascal开发环境,提供了丰富的工具和库来支持多线程编程。本篇文章将深入...

    java 多线程操作数据库

    ### Java多线程操作数据库:深入解析与应用 在当今高度并发的应用环境中,Java多线程技术被广泛应用于处理数据库操作,以提升系统的响应速度和处理能力。本文将基于一个具体的Java多线程操作数据库的应用程序,深入...

    稳定、方便、实用的VB6多线程技术(附老马的ActiveX多线程示例)

    VB6中的多线程主要通过两种方式实现:一是使用Microsoft的ActiveX EXE组件,二是通过API函数进行底层操作。下面将详细介绍这两种方法。 1. **ActiveX EXE组件**: 微软官方提供了ActiveX EXE组件,这是一种支持多...

    利用VB6实现多线程

    二、VB6实现多线程 在VB6中,创建多线程主要依靠ActiveX EXE组件。这个组件允许我们创建一个独立的线程来运行特定的代码,而不会阻塞主应用程序的执行。 1. 创建ActiveX EXE工程 首先,我们需要创建一个新的...

    java多线程编程(第二版)

    《Java多线程编程(第二版)》这本书深入探讨了这一主题,旨在帮助开发者更好地理解和应用Java的并发特性。 在Java中,多线程允许程序同时执行多个任务,提高应用程序的效率和响应性。核心概念包括线程的创建、同步...

    Visual Basic 6.0建立多线程程序就是这么简单

    #### 二、多线程简介 多线程是指在单个进程中创建并运行多个独立执行单元的技术。每个线程可以并发执行不同的任务,从而提高程序的整体性能。在VB6中,可以通过调用Windows API函数或利用计时器(Timer)控件来实现多...

    Android多线程文件上传

    在Android应用开发中,文件上传是一项常见的任务,尤其是在处理大文件或者需要提高用户交互体验时,多线程技术显得尤为重要。本主题聚焦于"Android多线程文件上传",我们将探讨如何利用多线程技术来优化文件上传过程...

    Linux系统下的多线程编程入门.pdf

    在Linux系统下进行多线程编程是开发高效并发应用程序的关键技术之一。本文将深入探讨Linux环境中的多线程概念、创建与管理线程的方法、线程同步与通信机制,以及多线程编程中可能遇到的问题和解决策略。 一、多线程...

    实验二、嵌入式Linux多线程编程实验

    实验二的目的是让学生深入理解嵌入式Linux环境下的多线程编程,这涉及到对线程概念、创建和管理的理解,以及如何在编程中引入线程库。线程是操作系统资源调度的基本单位,允许在一个进程中并发执行多个执行路径,...

    深入浅出 Java 多线程.pdf

    二、多线程编程的优点 多线程编程有以下几个优点: 1. 提高程序的执行效率:多线程编程可以同时执行多个任务,从而提高程序的执行效率。 2. 提高程序的响应速度:多线程编程可以提高程序的响应速度,因为它可以...

Global site tag (gtag.js) - Google Analytics