Java线程池

来源:互联网 发布:java登录界面设计代码 编辑:程序博客网 时间:2024/06/07 19:23

一、线程池概念

        多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。

        假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。

        如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。

        一个线程池包括以下四个基本组成部分:

        1、线程池管理器(ThreadPool):用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务;

        2、工作线程(PoolWorker):我们把用来执行用户任务的线程称为工作线程,工作线程就是不断从队列中获取任务对象并执行对象上的业务方法。 线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;

        3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;

        4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

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

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

        假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。

二、为什么使用线程池?

        线程池属于对象池。所有对象池都具有一个非常重要的共性,就是为了最大程度复用对象。那么线程池的最重要的特征也就是最大程度利用线程。

        首先,创建线程本身需要额外(相对于执行任务而必须的资源)的开销。

        作业系统在每创建一个线程时,至少需要创建以下资源:

        (1)线程内核对象:用于对线程上下文的管理。

        (2)用户模式执行栈。

        (3)内核模式执行栈。

        这些资源被线程占有后作业系统和用户都无法使用。

        相反的过程,销毁线程需要回收资源,也需要一定开销。

        其次,过多的线程将导致过度的切换。线程切换带来的性能更是不可估量。系统完成线程切换要经过以下过程:

        (1)从用户模式切换到内核模式。

        (2)将CPU寄存器的值保存到当前线程的内核对象中。

        (3)打开一个自旋锁,根据调度策略决定下一个要执行的线程。释放自旋锁,如果要执行的线程不是同一进程中的线程,还需要切换虚拟内存等进程环境。

        (4)将要执行的线程的内核对象的值写到CPU寄存器中。

        (5)切换到用户模式执行新线程的执行逻辑。

        所以线程池的目的就是为了减少创建和切换线程的额外开销,利用已经的线程多次循环执行多个任务从而提高系统的处理能力

三、使用线程池应遵循的原则:

        1、如果任务A在执行过程中需要同步等待任务B的执行结果→任务A不适合加入到线程池的工作队列→如果把像任务A一样的需要等待其他任务执行结果的任务加入到工作队列中→可能会导致线程池的死锁。

        2、如果执行某个任务可能会阻塞并且会长时间阻塞→应该设定超时时间→避免工作线程永久的阻塞下去→导致线程泄露→

        →服务器程序中→线程等待client连接 或者 等待client发送的数据时都可能会阻塞→

        (1)ServerSocket#setSoTimeout(int timeout) →设定等待client连接的超时时间

        (2)对于每个与client连接的Socket→Socket#setSoTimeout(int timeout) →设定等待client发送数据的超时时间。

        3、了解任务的特点:

        (1)分析任务时经常会执行阻塞的io操作→还是执行一直不会阻塞的运算操作→前者时断时续的占用cpu,而后者对cpu具有更高的利用率→预计完成任务的大概时间→是短时间任务还是长时间任务?→根据任务的特点→对任务进行分类→不同类型的任务加入到不同线程池的工作队列中→根据任务的特点→分别调整每个线程池。

        4、调整线程池的大小:

        (1)线程池的最佳大小主要取决于系统的可用cpu的数目以及工作队列中任务的特点。

        (2)如一个具有n个cpu的os上只有一个工作队列且其中全部是运算性质的任务,即不会阻塞的任务,则线程中具有n或n+1个工作线程时,一般会获得最大的cpu利用率。

        (3)如果工作队列中包含会执行I/O操作并常常阻塞的任务,则要让线程池的大小超过可用cpu的数目,因为并不是所有工作线程都一直在工作。选择一个典型的任务,然后估计在执行这个任务的过程中,等待时间(WT)与实际占用CPU进行运算的时间(ST)之间的比例WT/ST。对于一个具有N个CPU的系统,需 要设置大约N×(1+WT/ST)个线程来保证CPU得到充分利用。注-(n +平均阻塞的线程)

        (4)CPU利用率不是调整线程池大小过程中唯一要考虑的事项。随着线程池中工作线程数目的增长,还会碰到内存或者其他系统资源的限制,如套接字、打开的文件句柄或数据库连接数目等。要保证多线程消耗的系统资源在系统的承载范围之内。

        (5)避免任务过载。服务器应根据系统的承载能力,限制客户并发连接的数目。当客户并发连接的数目超过了限制值,服务器可以拒绝连接请求,并友好地告知客户:服务器正忙,请稍后再试.

四、使用线程池的注意事项:

        1、死锁

        任何多线程应用程序都有死锁风险。当一组进程或线程中的每一个都在等待一个只有该组中另一个进程才能引起的事件时,我们就说这组进程或线程 死锁了。死锁的最简单情形是:线程 A 持有对象 X 的独占锁,并且在等待对象 Y 的锁,而线程 B 持有对象 Y 的独占锁,却在等待对象 X 的锁。除非有某种方法来打破对锁的等待(Java 锁定不支持这种方法),否则死锁的线程将永远等下去。

        (1)A线程持有对象X的锁并等待对象Y的锁→B线程持有对象Y的锁并等待对象X的锁→A,B线程都不释放自己持有的锁→并且等待对方的锁→导致两个线程永远等待下去→产生死锁

        (2)线程池死锁→所有工作线程都在执行各自任务时被阻塞→等待某个任务A的执行结果→而任务A仍在工作队列中→没有空闲线程→A一直不能被执行→是的线程池的所有工作线程都永远阻塞→死锁→

        2、系统资源不足

        线程消耗包括内存和其它系统资源在内的大量资源。除了Thread对象所需的内存之外,每个线程都需要两个可能很大的执行调用堆栈。除此以外,JVM 可能会为每个 Java 线程创建一个本机线程,这些本机线程将消耗额外的系统资源。最后,虽然线程之间切换的调度开销很小,但如果有很多线程,环境切换也可能严重地影响程序的性能。

        除了线程自身所使用的资源以外,服务请求时所做的工作可能需要其它资源,例如 JDBC 连接、套接字或文件。这些也都是有限资源,有太多的并发请求也可能引起失效,例如不能分配 JDBC 连接。

        线程池线程数目多→消耗内存和其他系统资源→影响系统性能。

        3、并发错误

        ThreadPoolImpl的工作队列依靠wait和notify使工作线程即使取得任务→如果编码不正确,可能会丢失通知→导致工作线程一直保持空闲状态→无视工作队列需要处理的任务→使用这些方法必须格外小心→最好使用现有的,比较成熟的线程池→如concurrent包中的线程池类。

        4、线程泄露

        (1)对于工作线程数目固定的线程池→如果工作线程在执行任务抛出RuntimeException或Error→并且这些异常或错误没有被捕获→工作线程就会异常终止→线程池失去了一个工作线程→如果所有的工作线程都异常终止→线程池就为空→

        (2)工作线程在执行一个任务被阻塞→如等待用户输入数据→但由于用户一直不输入数据→导致工作线程一直被阻塞→这样的工作线程名存实亡→不执行任何任务了→如果线程池中所有工作线程都处于这样的阻塞状态→线程池无法处理新加入的任务。

        5、任务过载

        仅仅是请求就压垮了服务器,这种情况是可能的。在这种情形下,我们可能不想将每个到来的请求都排队到我们的工作队列,因为排在队列中等待执行的任务可能会消耗太多的系统资源并引起资源缺乏。在某些情况下,可以简单地抛弃请求,依靠更高级别的协议稍后重试请求,也可以用一个指出服务器暂时很忙的响应来拒绝请求。

        当工作队列中有大量排队等待执行的任务时→这些任务本身可能消耗太多系统资源而引起系统资源缺乏。

0 0
原创粉丝点击