Java5相比以前的Java版本,在并发编程上,有了很是大的提升,加了不少类,提供了不少可用于并发编程的工具包和工具类。尤为为人们所称道的,就是Java自带的线程池。 html
Java5线程池的介绍文章,能够说在网上比比皆是,我就再也不重复了,只是简单提一下,线程池给并发程序带 来了几个好处: java
一、建立和销毁线程的开销 编程
二、保护系统资源,避免建立太多的线程致使系统崩溃 api
三、简化编程模型 缓存
Java5自带的线程池( ThreadPool),用于并发系统的,主要有: 服务器
缓存线程池(newCachedThreadPool):每一个任务过来后都会建立一个线程,任务结束后,线程缓存一段时间,下次任务过来后,若是有以前缓存的线程就无须再建立而是直接使用。 并发
固定数量线程池(newFixedThreadPool):建立固定线程数量的线程池,若是任务数大于线程池中线程的数量,那么任务将等待。 oracle
其实,咱们看Java的源代码,就会发现,上面两种线程池,都是调用Java的ThreadPoolExecutor来实现的。其构造函数以下: 函数
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) |
咱们能够看到,其实是须要制定核心线程数、最大线程数、存活时间和时间单位、选择的队列这些参数。 工具
其中,核心线程数的概念相当重要,当有任务到来时,若是线程池中的线程数还不够核心线程数,则启动一个新的线程来运行任务(即使其它线程在空闲);若是线程池中的线程已经达到核心数,但未达到最大数,则任务放到队列中等待空闲的核心线程处理;除非队列满,不会启动新的线程。
从这里,咱们再来看缓存线程池和固定数量线程池,很是简单可以看出,缓存线程池的核心数为1,最大数为1,队列长度是1;固定线程池核心数和最大数都是设定的数量。
由于Java提供了这两种线程池,因此通常状况下,咱们可能都以为不须要再写别的线程池实现了,直接用这两种吧。结果在产品开发的过程当中,笔者就遇到了很大的一个坑,差点掉进去出不来。
当时是要开发一款高性能、高稳定性的C/S模式的服务器产品,Java5以后,使用线程池改进了任务派发的部分,当时觉得这两种线程池都应该可以知足要求,就先采用固定数量线程池来试,结果发现若是服务器和客户端都采用长链接,固定数量线程池存在很大的设计上的bug。
例如任务线程池核心数100,最大数100,客户端也正好100个链接连上了服务器。由于长链接机制下,为了保证处理效率,流程是服务器端ServerSocket在accept以后,不停循环,接受到请求后处理,而后继续等待链接上的数据,直至接收到客户端断开的指令或服务器端超时。
以下图:
能够想象,若是这时候第101个链接连上了服务器,再没有新的链接,TCP链接正常,任务也被接受放到了队列里,那么这个任务就只能乖乖等在队列里等着超时,别的什么都作不了!除非改变上面流程图中的方式,线程再也不这样循环,而是直接返回,这个链接上新的数据过来由另外的线程处理,等待线程池从新分配,但这样效率必定不如图中的方式好,不然没有什么好的办法。
对于一些使用场景复杂的服务器端,客户端长链接和短链接均可能有的这种场景,使用固定大小线程池,就必须考虑这个问题。或者牺牲效率,或者深刻考虑长连线程带来的问题。
既然固定大小有问题,那就看看缓存线程池吧,麻烦更大了,若是服务器没有别的辅助控制,一会儿涌入大量的客户链接,服务器一会儿须要启动大量的线程,颇有可能崩溃,这个也是没法接受的。
从这里能够看到,Java自带的两种线程池,实际上是各有各自的适用场景,对池中的任务,也都有本身的要求和限制,在适用这些基础设施来设计系统以前,首先应该对这些进行透彻的分析,不要等到出现bug才醒悟。上面提到的bug,由于没有清晰的错误信息,就很是不容易分析出来。
若是你的服务器面临这么复杂多变的客户端,并且既要求效率上不能牺牲,又但愿使用Java的线程基础设施,那么必定要在这里慎重再慎重。