java executor多线程同时运行其中一个找到结果退出_实现简单服务器学习多线程与Executor...

ad4b52318b4afca9d92fdf41aeb3b94c.png

实现简单的服务器

一个最简单的服务器实现如下图:

89477ed62f0a3db6a893831980d5f9cb.png

这是一个单线程的实现,也能满足基本的要求,但是如果请求多起来以后就会出现问题,由于同一时刻只能处理一个请求,服务器的响应性和吞吐量会急剧下降,改进成多线程模式如下图:

fbd23e6e9fbd96587262e4e29a944b91.png

多线程模式响应性和吞吐量都有所增加,但是创建线程和线程竞争都会消耗资源,所以它反而有可能会降低响应性,同时如果并发请求太多,会创建过多的线程,还会造成内存溢出导致程序崩溃。

每个请求都是一个任务,一个任务的执行都需要一个线程,但是又要控制线程创建的数量,Java中提供了线程池来支持这种方式,而线程池作为Executor框架的一部分。

Executor框架

Executor基于生产者、消费者模式,提交任务相当于消费者,执行任务相当于消费者,所以要实现一个生产者、消费者,最简单的方式就是通过Executor,生产者、消费者模式可以对任务的提交和执行进行解耦,因为任务的提交可能在工程的任何地方,但是执行一般只在一个位置,后期如果要对任务的执行进行修改就比较简单。

根据Executor方式来优化上面的例子如下图:

b0450a6890be70eb37337247632a933d.png

通过线程池的方式可以重复利用线程,避免线程的创建和销毁所消耗的资源,同时一个新任务过来时由于线程已经创建,可以马上执行也一定的提高了响应,创建足够的线程去执行任务,也可以限制线程数量太多导致的线程竞争造成的内存消耗,所以以后“new Thread(runnable).start();”这类代码都可以考虑用Executor来替换。

线程池主要有以下4种:

newFixedThreadPool:固定长度线程池,提交一个任务创建一个线程,直到达到最大数量。如果某个线程出现错误没有正常结束,会新建一个线程补充。

newCachedThreadPool:可缓存的线程池,如果线程池数量超过当前处理需求将回收空闲线程,当需求增加时可以添加新线程,线程池规模不存在任何限制;

newSingleThreadExecutor:单线程,创建单个工作线程,如果线程异常,会创建另一个替代,保证任务串行执行。

newScheduledThreadPool:固定长度线程池,可以以延迟或定时的方式来执行任务

生命周期管理ExecutorService

同时通过对executor的使用也可以实现各种调优、管理、监视、记录日志、错误报告等其他功能,不过到目前只了解了executor提交和执行,如果在执行的过程中要结束服务,当然还需要对任务的结束,ExecutorService就扩展了executor提供了这个功能,主要提供了以下两个方法:

shutdown方法:将执行平缓的关闭过程,不再接受新任务,同时等待已经提交的任务执行完成,包括还没有开始的任务。

shutdownNow方法:粗暴执行,尝试取消所有运行中的任务,并不再启动队列中未执行的任务。

任务带返回结果

Runnable不能返回一个值或者抛出一个受检查的异常,对需要有返回值的请求并不支持,在Executor框架中提供了Callable与Future接口来实现有返回值的任务。Future提供了get方法来获取返回值,如果任务没有完成get方法会阻塞直到任务完成返回。

FutureTask同时实现了Runnable和Future,所以可以把一个任务实例化成FutureTask然后交给ExecutorService去执行,然后再通过FutureTask的get方法获取各自的返回值。

局限

前面讲的例子都是基于多个请求,他们各自任务没有任何相关性,所以在多线程情况下能够显著的提高性能和响应率,不过某些情况下多线程对性能提升可能并不那么明显。

比如一个任务能够分成2个任务完成,但是其中一个任务所花费的时间是另外一个任务的10倍,那么即使是这两个任务并发的执行所花费时间总的提升并不大。

渲染网页示例

现在用Java代码去渲染一个HTML,由于HTML包含文字和图片,而图片只有url需要再去下载需要消耗大量的时间,所以可以把渲染文本和下载图片分成两个任务执行,文本渲染的时候预留图片的位置,通过ExecutorService的submit方法提交下载图片任务,他会返回一个Future,通过Future的get方法获取到下载的图片然后再进行图片渲染。

这里只分了两个任务,但是一般一个html图片不止一张,下载的时间很长,当所有图片下载完采取渲染,总体渲染时间提升不大。可以一张图片建一个任务,多个下载任务一起提交,然后遍历所有任务对应的Future集合,每下载一张图片就渲染一张这样总体就快了很多了,不过通过我们自己去处理比较麻烦,Java提供了一个更好的方法:完成服务(CompletionService)

完成服务(CompletionService)

CompletionService是Executor和BlockingQueue的功能结合体,可以将Callable任务提交给他来执行,然后使用类似队列的poll方法来获取已完成的结果,这些结果会再完成时将被封装为Future,ExecutorCompletionServiceCompletionService的实现类,它包含一个Executor和一个BlockingQueue,ExecutorCompletionService部分源码如下图:

4736242dc7bcca76be55539587d875d2.png

ExecutorCompletionService的submit方法没有截出来,它接受的是一个Callable的参数,这个Callable最终会通过上图中的方法转变成一个QueueingFuture放到exector中去执行,QueueingFuture中重写了done方法把task放入到队列中去,所以可以执行多次submit,然后调用ExecutorCompletionService的take或者poll方法获取执行结果,其中还有一个“poll(long timeout, TimeUnit unit)”方法还可以规定阻塞的时间。

总结

通过创建一个最简单的服务器来学习我们为什么需要线程池以及Java中对线程池的支持,以及采用线程池的优势,线程池的实现方式,带返回结果的任务实现方式,多个任务通过Executor和BlockingQueue实现,以及Java对这个方案的实现ExecutorCompletionService。

Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!

09f5c5bbe1880d5de12095ff0160a992.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值