java中的Executors简介与多线程在网站上逐步优化的运用案例

news/2024/4/27 15:57:06/文章来源:https://blog.csdn.net/weixin_34319640/article/details/88682576

提供Executor的工厂类
图片描述

忽略了自定义的ThreadFactory、callable和unconfigurable相关的方法
  • newFixedxxx:在任意时刻,最多有nThreads个线程在处理task;如果所有线程都在运行时来了新的任务,它会被扔入队列;如果有线程在执行期间因某种原因终止了运行,如果需要执行后续任务,新的线程将取代它

       return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
  • newCachedxxx:新任务到来如果线程池中有空闲的线程就复用,否则新建一个线程。如果一个线程超过60秒没有使用,它就会被关闭移除线程池

     return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
  • newSingleThreadExecutor:仅使用一个线程来处理任务,如果这线程挂了,会产生一个新的线程来代替它。每一个任务被保证按照顺序执行,而且一次只执行一个

      public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}
    使用newFixedxxx方法也能实现类似的作用,但是ThreadPoolExecutor会提供修改线程数的方法,FinalizableDelegatedExecutorService则没有修改的途径,它在DelegatedExecutorService的基础
    上仅提供了执行finalize时候去关闭线程,而DelegatedExecutorService仅暴漏ExecutorService自身的方法
  • newScheduledThreadPool:提供一个线程池来延迟或者定期执行任务

      public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,new DelayedWorkQueue());}
  • newSingleThreadScheduledExecutor:提供单个线程来延迟或者定期执行任务,如果执行的线程挂了,会生成新的。

      return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));
    同样,它保证返回的Executor自身的线程数不可修改

从上述的实现可以看出,核心在于三个部分

  • ThreadPoolExecutor:提供线程数相关的控制
  • DelegatedExecutorService:仅暴露ExecutorService自身的方法,保证线程数不变来实现语义场景
  • ScheduledExecutorService:提供延迟或者定期执行的功能

对应的,相应也有不同的队列去实现不同的场景

  • LinkedBlockingQueue:无界阻塞队列
  • SynchronousQueue:没有消费者消费时,新的任务就会被阻塞
  • DelayQueue:队列中的任务过期之后才可以执行,否则无法查询到队列中的元素

DelegatedExecutorService

它仅仅是包装了ExecutorService的方法,交由传入的ExecutorService来执行,所谓的UnConfigurable实际也就是它没有暴漏配置各种参数调整的方法

  static class DelegatedExecutorService extends AbstractExecutorService {private final ExecutorService e;DelegatedExecutorService(ExecutorService executor) { e = executor; }public void execute(Runnable command) { e.execute(command); }public void shutdown() { e.shutdown(); }public List<Runnable> shutdownNow() { return e.shutdownNow(); }public boolean isShutdown() { return e.isShutdown(); }public boolean isTerminated() { return e.isTerminated(); }public boolean awaitTermination(long timeout, TimeUnit unit)throws InterruptedException {return e.awaitTermination(timeout, unit);}public Future<?> submit(Runnable task) {return e.submit(task);}public <T> Future<T> submit(Callable<T> task) {return e.submit(task);}public <T> Future<T> submit(Runnable task, T result) {return e.submit(task, result);}public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)throws InterruptedException {return e.invokeAll(tasks);}public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException {return e.invokeAll(tasks, timeout, unit);}public <T> T invokeAny(Collection<? extends Callable<T>> tasks)throws InterruptedException, ExecutionException {return e.invokeAny(tasks);}public <T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException {return e.invokeAny(tasks, timeout, unit);}}

ScheduledExecutorService

提供一系列的schedule方法,使得任务可以延迟或者周期性的执行,对应schedule方法会返回ScheduledFuture以供确认是否执行以及是否要取消。它的实现ScheduledThreadPoolExecutor也支持立即执行由submit提交的任务

仅支持相对延迟时间,比如距离现在5分钟后执行。类似Timer也可以管理延迟任务和周期任务,但是存在一些缺陷:

  • 所有的定时任务只有一个线程,如果某个任务执行时间长,将影响其它TimerTask的精确性。ScheduledExecutorService的多线程机制可弥补
  • TimerTask抛出未检查的异常,将终止线程执行,此时会错误的认为任务都取消了。1:可以使用try-catch-finally对相应执行快处理;2:通过execute执行的方法可以设置UncaughtExceptionHandler来接收未捕获的异常,并作出处理;3:通过submit执行的,将被封装层ExecutionException重新抛出

ThreadPoolExecutor

 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}
  • corePoolSize、maximumPoolSize:ThreadPoolExecutor会根据这两自动调整线程池的大小,当一个新任务通过execute提交的时候:
    如果当前运行的线程数小于corePoolSize就新建线程;
    如果当前线程数在corePoolSize与maximumPoolSize之间,则只有在队列满的时候才会创建新的线程;
    如果已经达到最大线程数,并且队列都满了,在这种饱和状态下就会执行拒绝策略

    默认情况下,只有新任务到达的时候才会启动线程,可通过prestartCoreThread方法实现事先启动

    1. corePoolSize:默认线程池所需要维护的最小的worker的数量,就算是worker过期了也会保留。如果想要不保留,则需要设置allowCoreThreadTimeOut,此时最小的就是0
    2. maximumPoolSize:线程池最大的线程数。java限制最多为 2^29-1,大约5亿个
  • keepAliveTime、unit:如果当前线程池有超过corePoolSize的线程数,只要有线程空闲时间超过keepAliveTime的设定,就会被终止;unit则是它的时间单位
  • workQueue:任何BlockingQueue都可以使用,基本上有三种

    1. Direct handoffs,直接交付任务。比如 SynchronousQueue,如果没有线程消费,提交任务会失败,当然可以新建一个线程来处理。它适合处理有依赖关系的任务,一般它的maximumPoolSizes会被设置成最大的
    2. Unbounded queues,无界队列。比如LinkedBlockingQueue,这意味着如果有corePoolSize个线程在执行,那么其他的任务都只能等待。它适合于处理任务都是互相独立的,
    3. Bounded queues,有界队列。比如ArrayBlockingQueue,需要考虑队列大小和最大线程数之间的关系,来达到更好的资源利用率和吞吐量
  • threadFactory:没有指定的时候,使用Executors.defaultThreadFactory
  • RejectedExecutionHandler:通过execute添加的任务,如果Executor已经关闭或者已经饱和了(线程数达到了maximumPoolSize,并且队列满了),就会执行,java提供了4种策略:

    1. AbortPolicy,拒绝的时候抛出运行时异常RejectedExecutionException;
    2. CallerRunsPolicy,如果executor没有关闭,那么由调用execute的线程来执行它;
    3. DiscardPolicy,直接扔掉新的任务;
    4. DiscardOldestPolicy,如果executor没有关闭,那么扔掉队列头部的任务,再次尝试;
ThreadPoolExecutor可自定义beforeExecutor、afterExecutor可以用来添加日志统计、计时、件事或统计信息收集功能,无论run是正常返回还是抛出异常,afterExecutor都会被执行。如果beforeExecutor抛出RuntimeException,任务和afterExecutor都不会被执行。terminated在所有任务都已经完成,并且所有工作者线程关闭后会调用,此时也可以用来执行发送通知、记录日志等等。

如何估算线程池的大小

  1. 计算密集型,通常在拥有$N_{cpu}$个处理器的系统上,线程池大小设置为$N_{cpu}+1$能够实现最优的利用率;

    $N_{cpu}$ cpu的个数
  2. I/O密集型或者其它阻塞型的任务,定义 $N_{cpu}$为CPU的个数,$U_{cpu}$为CPU的利用率,$W/C$为等待时间与计算时间的比率,此时线程池的最优大小为

$$N_{threads}=N_{cpu}*U_{cpu}*(1+W/C)$$

场景说明

将一个网站的业务抽象成如下几块

  • 接收客户端请求与处理请求
  • 页面渲染返回的文本和图片
  • 获取页面的广告

接收请求与处理请求

理论模型

理论上,服务端通过实现约定的接口就可以实现接收请求和处理连续不断的请求过来

ServerSocket socket = new ServerSocket(80);
while(true){Socket conn = socket.accept();handleRequest(conn)
}

缺点:每次只能处理一个请求,新请求到来时,必须等到正在处理的请求处理完成,才能接收新的请求

显示的创建多线程

为每个请求创建新的线程提供服务

ServerSocket socket = new ServerSocket(80);
while(true){final Socket conn = socket.accept();Runnable task = new Runnable(){public void run(){handleRequest(conn);        }}new Thread(task).start();
}

缺点:

  • 线程的创建和销毁都有一定的开销,延迟对请求的处理;
  • 创建后的线程多于可用处理器的数量,造成线程闲置,这会给垃圾回收带来压力
  • 存活的大量线程竞争CPU资源会产生很多性能开销
  • 系统上对可创建的线程数存在限制

使用线程池

使用java自带的Executor框架。

private static final Executor exec = Executors.newFixedThreadPool(100);
...
ServerSocket socket = new ServerSocket(80);
while(true){final Socket conn = socket.accept();Runnable task = new Runnable(){public void run(){handleRequest(conn);        }}exec.execute(task);
}
...

线程池策略通过实现预估好的线程需求,限制并发任务的数量,重用现有的线程,解决每次创建线程的资源耗尽、竞争过于激烈和频繁创建的问题,也囊括了线程的优势,解耦了任务提交和任务执行。

页面渲染返回的文本和图片

串行渲染

renderText(source);
List<ImageData> imageData = new ArrayList<ImageData>();
for(ImageInfo info:scaForImageInfo(source)){imageData.add(info.downloadImage());
}
for(ImageData data:imageData){renderImage(data);
}

缺点:图像的下载大部分时间在等待I/O操作执行完成,这期间CPU几乎不做任何工作,使得用户看到最终页面之前要等待过长的时间

并行化

渲染过程可以分成两个部分,1是渲染文本,1是下载图像

private static final ExecutorService exec = Executors.newFixedThreadPool(100);
...
final List<ImageInfo> infos=scaForImageInfo(source);
Callable<List<ImageData>> task=new Callable<List<ImageData>>(){public List<ImageData> call(){List<ImageData> r = new ArrayList<ImageData>();for(ImageInfo info:infos){r.add(info.downloadImage());}return r;}
};
Future<List<ImageData>> future = exec.submit(task);
renderText(source);
try{List<ImageData> imageData = future.get();for(ImageData data:imageData){renderImage(data);}
}catch(InterruptedException e){Thread.currentThread().interrupt();future.cancel(true);
}catche(ExecutionException e){throw launderThrowable(e.getCause());
}

使用Callable来返回下载的图片结果,使用future来获得下载的图片,这样将减少用户所需要的等待时间。
缺点:图片的下载很明显时间要比文本要慢,这样的并行化很可能速度可能只提升了1%

并行性能提升

使用CompletionService。

private static final ExecutorService exec;
...
final List<ImageInfo> infos=scaForImageInfo(source);
CompletionService<ImageData> cService =  new ExecutorCompletionService<ImageData>(exec);
for(final ImageInfo info:infos){cService.submit(new Callable<ImageData>(){public ImageData call(){return info.downloadImage();}});
}
renderText(source);
try{for(int i=0,n=info.size();t<n;t++){Future<ImageData> f = cService.take();ImageData imageData=f.get();renderImage(imageData)}
}catch(InterruptedException e){Thread.currentThread().interrupt();
}catche(ExecutionException e){throw launderThrowable(e.getCause());
}

核心思路为为每一幅图像下载都创建一个独立的任务,并在线程池中执行他们,从而将串行的下载过程转换为并行的过程

获取页面的广告

广告展示如果在一定的时间以内没有获取,可以不再展示,并取消超时的任务。

 ExecutorService exe = Executors.newFixedThreadPool(3);List<MyTask> myTasks = new ArrayList<>();for (int i=0;i<3;i++){myTasks.add(new MyTask(3-i));}try {List<Future<String>> futures = exe.invokeAll(myTasks, 1, TimeUnit.SECONDS);for (int i=0;i<futures.size();i++){try {String s = futures.get(i).get();System.out.println("task execut "+myTasks.get(i).getSleepSeconds()+" s");} catch (ExecutionException e) {System.out.println("task sleep "+myTasks.get(i).getSleepSeconds()+" not execute ");}catch (CancellationException e){System.out.println("task sleep "+myTasks.get(i).getSleepSeconds()+" not execute ,because "+e);}}} catch (InterruptedException e) {e.printStackTrace();}exe.shutdown();  

invokeAll方法对于没有完成的任务会被取消,通过CancellationException可以捕获,invokeAll返回的序列顺序和传入的task保持一致。结果如下:

task sleep 3 not execute ,because java.util.concurrent.CancellationException
task sleep 2 not execute ,because java.util.concurrent.CancellationException
task execut 1 s

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_700896.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

爬取网站图片并保存到本地

第一步&#xff1a;模拟浏览器发出请求&#xff0c;获取网页数据 import requests# 目标网站 url https://baijiahao.baidu.com/s?id1687278509395553439&wfrspider&forpc # 头部伪装 headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Ge…

php网站安全狗绕过,最新安全狗绕过姿势 - Azeng呐的个人空间 - OSCHINA - 中文开源技术交流社区...

安全狗是让大家最头疼的安全防护软件&#xff0c;然后我给大家带来最新的安全狗绕过&#xff0c;也不知道能活多久。攻防永无止境吧。最新版本安全狗从官网下载的&#xff0c;我来说一下思路。要想绕过安全狗首先你要知道&#xff0c;安全狗是怎么防护的&#xff0c;过滤的是什…

使用C#的HttpWebRequest模拟登陆网站

很久没有写新的东西了&#xff0c;今天在工作中遇到的一个问题&#xff0c;感觉很有用&#xff0c;有种想记下来的冲动。 这篇文章是有关模拟登录网站方面的。 实现步骤&#xff1b; 启用一个web会话发送模拟数据请求&#xff08;POST或者GET&#xff09;获取会话的CooKie 并根…

Scrapy框架模拟Github网站登陆

1. 以往的模拟登陆的方法 1.1 requests模块是如何实现模拟登陆的&#xff1f; 直接携带cookies请求页面找url地址&#xff0c;发送post请求存储cookie 1.2 selenium是如何模拟登陆的&#xff1f; 找到对应的input标签&#xff0c;输入文本点击登陆 1.3 scrapy的模拟登陆 直…

Python爬虫并自制新闻网站,太好玩了

来源 | 凹凸数据&#xff08;ID&#xff1a;alltodata&#xff09;我们总是在爬啊爬&#xff0c;爬到了数据难道只是为了做一个词云吗&#xff1f;当然不&#xff01;这次我就利用flask为大家呈现一道小菜。Flask是python中一个轻量级web框架&#xff0c;相对于其他web框架来说…

Spring Boot 2.X整合Spring-cache,让你的网站速度飞起来

计算机领域有人说过一句名言&#xff1a;“计算机科学领域的任何问题都可以通过增加一个中间层来解决”&#xff0c;今天我们就用Spring-cache给网站添加一层缓存&#xff0c;让你的网站速度飞起来。本文目录 一、Spring Cache介绍二、缓存注解介绍三、Spring BootCache实战1、…

一步步构建大型网站架构

之前我简单向大家介绍了各个知名大型网站的架构&#xff0c;MySpace的五个里程碑、Flickr的架构、YouTube的架构、PlentyOfFish的架构、WikiPedia的架构。这几个都很典型&#xff0c;我们可以从中获取很多有关网站架构方面的知识&#xff0c;看了之后你会发现你原来的想法很可能…

利用WxJava实现PC网站集成微信登录功能,核心代码竟然不超过10行

最近网站PC端集成微信扫码登录&#xff0c;踩了不少坑&#xff0c;在此记录下实现过程和注意事项。本文目录 一、微信开放平台操作步骤1.创建“网站应用”2.获取AppID和AppSecret二、开发指南三、开发实战1、pom.xml引入jar包2、配置文件添加对应的配置3、初始化配置4、控制层核…

你为什么应该经常访问招聘网站?招聘网站至少有4个方面的价值!

一、缘起读大学的时候&#xff0c;有时候会感到很迷茫&#xff0c;不知道毕业之后可以做什么&#xff0c;自己能拿到多少的月薪。于是&#xff0c;就想到去参加一些公司的招聘。大二大三的时候&#xff0c;就去武大参加了武汉中地数码等3个公司的笔试。但是&#xff0c;没有交答…

从12306网站谈起虚拟主机选购注意事项

2019独角兽企业重金招聘Python工程师标准>>> “独在异乡为异客&#xff0c;每逢佳节倍思亲。”在临近年关的氛围下&#xff0c;尤其是只能通过火车才能归家的“游子”&#xff0c;是否也像笔者一样看着不能打开的12306网站望“票”兴叹呢&#xff1f;身为国内资深虚…

在任何设备上都完美呈现的30个华丽的响应式网站

如今&#xff0c;一个网站只在桌面屏幕上好看是远远不够的&#xff0c;同时也要在平板电脑和智能手机中能够良好呈现。响应式的网站是指它能够适应客户端的屏幕尺寸&#xff0c;自动响应客户端尺寸变化。在这篇文章中&#xff0c;我将向您展示在任何设备上都完美的30个华丽的响…

新浪微博推广网站的一些实践体会

本以为微博推广很难&#xff0c;每天都要刷粉刷内容的&#xff0c;也本以为做微博推广也很简单&#xff0c;一不卖产品、二不卖服务的&#xff0c;目的单纯灵活性强些&#xff0c;做了之后才发现都不是那么回事&#xff0c;微博虽然也过了“火了”&#xff0c;但新媒体还真是不…

windowsXP用户被禁用导致不能网站登录

1、查看系统事件&#xff0c;发现弹出如下的错误 2、根据上面的错误&#xff0c;我们很容易就可以判断是禁用了账户引起的 2.1后面进入计算机管理&#xff0c;再进入用户管理 2.2双击点开Internet来宾用于&#xff0c;发现此用户已经停用了。 2.3双击点开与IIS访问有关用户&…

AI 和 SEO 的结合:是福还是祸?

作者 | Vik Bogdanov翻译 | Katie,责编 | 晋兆雨头图 | 付费下载于视觉中国自成立以来&#xff0c;搜索引擎已经从基本搜索代理变成了基于人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;的复杂算法。这些创新技术从两个完全相反的角度影响搜索引擎优…

【云计算】云上建站快速入门:博客、论坛、CMS、电子商务网站统统

免费网站怎么建&#xff0c;空间也能免费吗?免费网站怎么建立&#xff0c;免费网站并非免费空间互联网真的有免费建站这等好事&#xff1f;现在制作一个网站已经越来越容易了&#xff0c;只要知道清晰的流程之后都是可以很快的建好一个企业或者个人网站的&#xff01;免费的建…

PrestaShop 网站后台配置(六)

转载请注明出处&#xff1a;http://www.cnblogs.com/zhong-dev/p/4943023.html 网店版本 Prestashop v1.6 配置邮箱店铺在客户下单之后&#xff0c;可以自动给客户发送邮件&#xff0c;要实现这个功能首先一点服务器要支持邮件功能。现在网店的运行环境是 amh 环境&#xff0c…

在 Azure 网站上使用 Memcached 改进 WordPress

编辑人员注释&#xff1a;本文章由 Windows Azure 网站团队的项目经理 Sunitha Muthukrishna 和 Windows Azure 网站开发人员体验合作伙伴共同撰写。 您是否希望改善在 Azure 网站服务上运行的 WordPress 网站的性能&#xff1f;如果是&#xff0c;那么您就需要一个可帮助加快您…

微软惹的祸!CVPR提交网站最后1小时被挤崩,官方紧急延长36小时

视学算法报道 编辑&#xff1a;小咸鱼 好困【新智元导读】CVPR提交网站宕机了&#xff0c;而且还是在截止时间前的最后1个小时&#xff01;于是DDL被紧急延长了1天半。什么&#xff1f;CVPR 2022的论文提交网站居然在deadline之前一个小时崩掉了&#xff01;赶着DDL提交论文的…

域名年龄-SEO搜索引擎优化

为什么80%的码农都做不了架构师&#xff1f;>>> 域名年龄-SEO搜索引擎优化 在我们创建一个新的网站时&#xff0c;我们首先考虑到的是去注册一个新的域名。 有时发现我们 要注册的域名已经被注册了&#xff0c;于是就有两种方式&#xff1a; 一、重新注册另外的…

网络空间安全之信息追踪——学习笔记 利用门户网站,综合信息追踪

企业信息追踪与防护&#xff1a; 对于一个公司来说&#xff0c;只要牵扯到公司任一信息&#xff0c;都可以称之为公司的机密文件&#xff01; 知名门户网站搜索&#xff1a; 新华网&#xff1a;http://www.xinhuanet.com/ 党中央直接部署的&#xff0c;重大影响力&#xff01; …