高性能聊天系统

  作者:板桥banq

上页

1.2.5  线程池

1.2.4节主要介绍了多线程使用时对资源争夺情况的处理,只要掌握几个基本原则,在一般情况下使用多线程实现应用处理就没有太大问题。

但是,当多线程数量很多时,每次启动线程的开销也非常大,有时,创建新线程的服务器在创建和销毁线程上花费的时间和消耗的资源,可能要比花在处理应用逻辑运算的的时间和资源要多得多。

除了创建和销毁线程的开销之外,活动的线程也会消耗系统资源。当应用系统突然遭遇巨大访问量访问时,服务器内存中会创建太多的线程,直至资源完全消耗,这对于应用系统的正常运行是有致命伤害的。

为了能在访问尖峰时限制线程开启数目,以及减少线程频繁创建和销毁带来的系统开销,提高系统的大访问量处理性能和速度,需要事先创建一定数量的线程供调用者循环反复使用,这也就是“池”技术。

线程池的基本原理也是基于队列Queue实现,通过不断查询队列Queue是否有可以运行的线程。如果有,就立即运行线程;如果没有,就锁定等待,直至有新的线程加入被触发解锁。

线程池作为一种比较成熟的技术,一般不需要自己开发设计,因为性能优良的线程池不是很容易开发出来的,必须解决下列几个问题:死锁、资源不足、并发错误、线程泄漏和请求过载等。

因此,可以使用一些现成的经过很多实践证明可靠的线程池软件,其中比较著名的是Doug Lea 编写了一个优秀的开放源码库util.concurrenthttp://gee.cs.oswego. edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html),它包括互斥、信号量、诸如在并发访问下执行得很好的队列和散列表之类以及几个工作队列实现。

使用PooledExecutor 类就可以将自己的线程放入线程池中运行。举例如下:

PooledExecutor pool = new PooledExecutor(new BoundedBuffer(20), 100);

pool.setMinimumPoolSize(10); //最小数是10

pool.setKeepAliveTime( -1); //线程一直运行

该语句设置了线程池的最大线程数是100,这样,保护系统不会因为访问量增加导致线程数目无限制增加。使用该线程池如下:

pool .execute(java.lang.Runnable 自己的线程);                                   

这一句实际上是将“自己的线程”加入一个队列中,而队列另外一端正开启着多个线程不断读取这个队列,一旦队列中有空闲的线程,ThreadWorker将读取并分配线程来运行它。execute代码如下:

public void execute(Runnable command) throws InterruptedException {

    for (;;) {                                                         //一直循环 运行

      synchronized(this) {

        if (!shutdown_) {                                  //确保线程池没有被关闭

          int size = poolSize_;                          //当前线程池中线程的数目

          // Ensure minimum number of threads

          if (size < minimumPoolSize_) {        //如果当前线程数目小于线程池最小数目

            addThread(command);                    //增加新线程,并运行command线程

            return;

          }

          //如果目前线程池中有超过或等于最小数目的线程

          //分配一个存在的空闲线程来运行command,handOff是队列

          if (handOff_.offer(command, 0)) {

            return;

          }

          // 如果不能分配已有的线程来运行command,那么创建一个新的线程

          if (size < maximumPoolSize_) {

            addThread(command);

            return;

          }

        }

      }

      // Cannot hand off and cannot create -- ask for help

      if (getBlockedExecutionHandler().blockedAction(command)) {

        return;

      }

    }

  }

}

由以上代码可见,PooledExecutor线程池运行原理是,当执行execute加载一个应用系统的线程池时,线程池内部首先检查当前线程数目有无达到线程池设定的最小数目。如果没有,启动新线程运行;如果已经达到了,那么检查有无空闲可再用的线程来运行;如果没有,就再创建新的线程,直至达到最大线程数目。

因此,使用线程池有两个最大好处:首先是循环使用,一经创建后,空闲的线程可以被再次反复使用,提高了线程运行效率;其次是有最大线程数限制,当系统达到最大数目时,将不再分配线程池中线程来运行,从而保证了系统资源的可用性,防止系统资源不足,甚至耗尽。

在实际应用中,要根据处理内容来决定是否使用线程池,并不是说所有处理功能都可以使用线程池实现。只有那些处理完成速度快,可以在很短时间内结束访问的处理功能才可以使用线程池。J2EE中是采取线程池结合对象池的底层运行机制,通过JSP/Servlet线程访问对象池中的无状态Session Beans来实现复杂耗时功能的处理,这种处理机制已经证明是一种成熟稳定的办法。

本系统中,聊天系统核心功能的处理可以采取线程池设计,这样可以加快对每次聊天信息的处理时间和提高处理速度。

下页