【廖雪峰官方网站/Java教程】多线程(3)

news/2024/5/20 4:35:37/文章来源:https://blog.csdn.net/Allenlzcoder/article/details/105926875

1.使用线程池

1.1.ExecutorService介绍

Java语言虽然内置了多线程支持,启动一个新线程非常方便,但是,创建线程需要操作系统资源(线程资源,栈空间等),频繁创建和销毁大量线程需要消耗大量时间。
如果可以复用一组线程:
在这里插入图片描述
那么我们就可以把很多小任务让一组线程来执行,而不是一个任务对应一个新线程。这种能接收大量小任务并进行分发处理的就是线程池。
Java标准库提供了ExecutorService接口表示线程池,它的典型用法如下:

// 创建固定大小的线程池:
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交任务:
executor.submit(task1);
executor.submit(task2);
executor.submit(task3);
executor.submit(task4);
executor.submit(task5);

因为ExecutorService只是接口,Java标准库提供的几个常用实现类有:

  • FixedThreadPool:线程数固定的线程池;
  • CachedThreadPool:线程数根据任务动态调整的线程池;
  • SingleThreadExecutor:仅单线程执行的线程池。

创建这些线程池的方法都被封装到Executors这个类中。我们以FixedThreadPool为例,看看线程池的执行逻辑:

// thread pool
import java.util.concurrent.*;public class Main {public static void main(String[] args) {// 创建一个固定大小的线程池:ExecutorService es = Executors.newFixedThreadPool(4);for (int i = 0; i < 6; i++) {es.submit(new Task("" + i));}// 关闭线程池:es.shutdown();}
}class Task implements Runnable {private final String name;public Task(String name) {this.name = name;}@Overridepublic void run() {System.out.println("start task " + name);try {Thread.sleep(1000);} catch (InterruptedException e) {}System.out.println("end task " + name);}
}

我们观察执行结果,一次性放入6个任务,由于线程池只有固定的4个线程,因此,前4个任务会同时执行,等到有线程空闲后,才会执行后面的两个任务。
线程池在程序结束的时候要关闭。使用shutdown()方法关闭线程池的时候,它会等待正在执行的任务先完成,然后再关闭。shutdownNow()会立刻停止正在执行的任务,awaitTermination()则会等待指定的时间让线程池关闭。
如果我们把线程池改为CachedThreadPool,由于这个线程池的实现会根据任务数量动态调整线程池的大小,所以6个任务可一次性全部同时执行。
如果我们想把线程池的大小限制在4~10个之间动态调整怎么办?我们查看Executors.newCachedThreadPool()方法的源码:

public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}

因此,想创建指定动态范围的线程池,可以这么写:

int min = 4;
int max = 10;
ExecutorService es = new ThreadPoolExecutor(min, max,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

1.2.ScheduledThreadPool

还有一种任务,需要定期反复执行,例如,每秒刷新证券价格。这种任务本身固定,需要反复执行的,可以使用ScheduledThreadPool。放入ScheduledThreadPool的任务可以定期反复执行。
创建一个ScheduledThreadPool仍然是通过Executors类:

ScheduledExecutorService ses = Executors.newScheduledThreadPool(4);

我们可以提交一次性任务,它会在指定延迟后只执行一次:

// 1秒后执行一次性任务:
ses.schedule(new Task("one-time"), 1, TimeUnit.SECONDS);

如果任务以固定的每3秒执行,我们可以这样写:

// 2秒后开始执行定时任务,每3秒执行:
ses.scheduleAtFixedRate(new Task("fixed-rate"), 2, 3, TimeUnit.SECONDS);

如果任务以固定的3秒为间隔执行,我们可以这样写:

// 2秒后开始执行定时任务,以3秒为间隔执行:
ses.scheduleWithFixedDelay(new Task("fixed-delay"), 2, 3, TimeUnit.SECONDS);

注意FixedRate和FixedDelay的区别。FixedRate是指任务总是以固定时间间隔触发,不管任务执行多长时间:
在这里插入图片描述
而FixedDelay是指,上一次任务执行完毕后,等待固定的时间间隔,再执行下一次任务:
在这里插入图片描述
因此,使用ScheduledThreadPool时,我们要根据需要选择执行一次、FixedRate执行还是FixedDelay执行。
Java标准库还提供了一个java.util.Timer类,这个类也可以定期执行任务,但是,一个Timer会对应一个Thread,所以,一个Timer只能定期执行一个任务,多个定时任务必须启动多个Timer,而一个ScheduledThreadPool就可以调度多个定时任务,所以,我们完全可以用ScheduledThreadPool取代旧的Timer。

1.3.小结

JDK提供了ExecutorService实现了线程池功能:

  1. 线程池内部维护一组线程,可以高效执行大量小任务;
  2. Executors提供了静态方法创建不同类型的ExecutorService;
  3. 必须调用shutdown()关闭ExecutorService;
  4. ScheduledThreadPool可以定期调度多个任务。

2.使用Future

2.1.Runnable和Callable接口介绍

在执行多个任务的时候,使用Java标准库提供的线程池是非常方便的。我们提交的任务只需要实现Runnable接口,就可以让线程池去执行:

class Task implements Runnable {public String result;public void run() {this.result = longTimeCalculation(); }
}

Runnable接口有个问题,它的方法没有返回值。如果任务需要一个返回结果,那么只能保存到变量,还要提供额外的方法读取,非常不便。所以,Java标准库还提供了一个Callable接口,和Runnable接口比,它多了一个返回值:

class Task implements Callable<String> {public String call() throws Exception {return longTimeCalculation(); }
}

并且Callable接口是一个泛型接口,可以返回指定类型的结果。
现在的问题是,如何获得异步执行的结果?
如果仔细看ExecutorService.submit()方法,可以看到,它返回了一个Future类型,一个Future类型的实例代表一个未来能获取结果的对象:

ExecutorService executor = Executors.newFixedThreadPool(4); 
// 定义任务:
Callable<String> task = new Task();
// 提交任务并获得Future:
Future<String> future = executor.submit(task);
// 从Future获取异步执行返回的结果:
String result = future.get(); // 可能阻塞

当我们提交一个Callable任务后,我们会同时获得一个Future对象,然后,我们在主线程某个时刻调用Future对象的get()方法,就可以获得异步执行的结果。在调用get()时,如果异步任务已经完成,我们就直接获得结果。如果异步任务还没有完成,那么get()会阻塞,直到任务完成后才返回结果。
一个Future<V>接口表示一个未来可能会返回的结果,它定义的方法有:

  • get():获取结果(可能会等待)
  • get(long timeout, TimeUnit unit):获取结果,但只等待指定的时间;
  • cancel(boolean mayInterruptIfRunning):取消当前任务;
  • isDone():判断任务是否已完成。

2.2.小结

  1. 对线程池提交一个Callable任务,可以获得一个Future对象;
  2. 可以用Future在将来某个时刻获取结果。

3.使用CompletableFuture【待理解】

3.1.CompletableFuture接口介绍示例

使用Future获得异步执行结果时,要么调用阻塞方法get(),要么轮询看isDone()是否为true,这两种方法都不是很好,因为主线程也会被迫等待。
从Java 8开始引入了CompletableFuture,它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法
我们以获取股票价格为例,看看如何使用CompletableFuture:

// CompletableFuture
import java.util.concurrent.CompletableFuture;public class Main {public static void main(String[] args) throws Exception {// 创建异步执行任务:CompletableFuture<Double> cf = CompletableFuture.supplyAsync(Main::fetchPrice);// 如果执行成功:cf.thenAccept((result) -> {System.out.println("price: " + result);});// 如果执行异常:cf.exceptionally((e) -> {e.printStackTrace();return null;});// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:Thread.sleep(200);}static Double fetchPrice() {try {Thread.sleep(100);} catch (InterruptedException e) {}if (Math.random() < 0.3) {throw new RuntimeException("fetch price failed!");}return 5 + Math.random() * 20;}
}

创建一个CompletableFuture是通过CompletableFuture.supplyAsync()实现的,它需要一个实现了Supplier接口的对象:

public interface Supplier<T> {T get();
}

这里我们用lambda语法简化了一下,直接传入Main::fetchPrice,因为Main.fetchPrice()静态方法的签名符合Supplier接口的定义(除了方法名外)。
紧接着,CompletableFuture已经被提交给默认的线程池执行了,我们需要定义的是CompletableFuture完成时和异常时需要回调的实例。完成时,CompletableFuture会调用Consumer对象:

public interface Consumer<T> {void accept(T t);
}

异常时,CompletableFuture会调用Function对象:

public interface Function<T, R> {R apply(T t);
}

这里我们都用lambda语法简化了代码。
可见CompletableFuture的优点是:

  1. 异步任务结束时,会自动回调某个对象的方法;
  2. 异步任务出错时,会自动回调某个对象的方法;
  3. 主线程设置好回调后,不再关心异步任务的执行。

3.2.多个CompletableFuture串行执行

如果只是实现了异步回调机制,我们还看不出CompletableFuture相比Future的优势。CompletableFuture更强大的功能是,多个CompletableFuture可以串行执行,例如,定义两个CompletableFuture,第一个CompletableFuture根据证券名称查询证券代码,第二个CompletableFuture根据证券代码查询证券价格,这两个CompletableFuture实现串行操作如下:

// CompletableFuture
import java.util.concurrent.CompletableFuture;public class Main {public static void main(String[] args) throws Exception {// 第一个任务:CompletableFuture<String> cfQuery = CompletableFuture.supplyAsync(() -> {return queryCode("中国石油");});// cfQuery成功后继续执行下一个任务:CompletableFuture<Double> cfFetch = cfQuery.thenApplyAsync((code) -> {return fetchPrice(code);});// cfFetch成功后打印结果:cfFetch.thenAccept((result) -> {System.out.println("price: " + result);});// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:Thread.sleep(2000);}static String queryCode(String name) {try {Thread.sleep(100);} catch (InterruptedException e) {}return "601857";}static Double fetchPrice(String code) {try {Thread.sleep(100);} catch (InterruptedException e) {}return 5 + Math.random() * 20;}
}

3.3.多个CompletableFuture并行执行

例如,我们考虑这样的场景:同时从新浪和网易查询证券代码,只要任意一个返回结果,就进行下一步查询价格,查询价格也同时从新浪和网易查询,只要任意一个返回结果,就完成操作:

// CompletableFuture
import java.util.concurrent.CompletableFuture;public class Main {public static void main(String[] args) throws Exception {// 两个CompletableFuture执行异步查询:CompletableFuture<String> cfQueryFromSina = CompletableFuture.supplyAsync(() -> {return queryCode("中国石油", "https://finance.sina.com.cn/code/");});CompletableFuture<String> cfQueryFrom163 = CompletableFuture.supplyAsync(() -> {return queryCode("中国石油", "https://money.163.com/code/");});// 用anyOf合并为一个新的CompletableFuture:CompletableFuture<Object> cfQuery = CompletableFuture.anyOf(cfQueryFromSina, cfQueryFrom163);// 两个CompletableFuture执行异步查询:CompletableFuture<Double> cfFetchFromSina = cfQuery.thenApplyAsync((code) -> {return fetchPrice((String) code, "https://finance.sina.com.cn/price/");});CompletableFuture<Double> cfFetchFrom163 = cfQuery.thenApplyAsync((code) -> {return fetchPrice((String) code, "https://money.163.com/price/");});// 用anyOf合并为一个新的CompletableFuture:CompletableFuture<Object> cfFetch = CompletableFuture.anyOf(cfFetchFromSina, cfFetchFrom163);// 最终结果:cfFetch.thenAccept((result) -> {System.out.println("price: " + result);});// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:Thread.sleep(200);}static String queryCode(String name, String url) {System.out.println("query code from " + url + "...");try {Thread.sleep((long) (Math.random() * 100));} catch (InterruptedException e) {}return "601857";}static Double fetchPrice(String code, String url) {System.out.println("query price from " + url + "...");try {Thread.sleep((long) (Math.random() * 100));} catch (InterruptedException e) {}return 5 + Math.random() * 20;}
}

上述逻辑实现的异步查询规则实际上是:
在这里插入图片描述
除了anyOf()可以实现“任意个CompletableFuture只要一个成功”,allOf()可以实现“所有CompletableFuture都必须成功”,这些组合操作可以实现非常复杂的异步流程控制。
最后我们注意CompletableFuture的命名规则:

  • xxx():表示该方法将继续在已有的线程中执行;
  • xxxAsync():表示将异步在线程池中执行。

3.4.小结

CompletableFuture可以指定异步处理流程:

  1. thenAccept()处理正常结果;
  2. exceptional()处理异常结果;
  3. thenApplyAsync()用于串行化另一个CompletableFuture;
  4. anyOf()和allOf()用于并行化多个CompletableFuture。

4.使用ForkJoin

Java 7开始引入了一种新的Fork/Join线程池,它可以执行一种特殊的任务:把一个大任务拆成多个小任务并行执行。

  1. Fork/Join是一种基于“分治”的算法:通过分解任务,并行执行,最后合并结果得到最终结果。
  2. ForkJoinPool线程池可以把一个大任务分拆成小任务并行执行,任务类必须继承自RecursiveTask或RecursiveAction。
  3. 使用Fork/Join模式可以进行并行计算以提高效率。

在此不做详细展示,链接:https://www.liaoxuefeng.com/wiki/1252599548343744/1306581226487842

5.使用ThreadLocal

  1. ThreadLocal表示线程的“局部变量”,它确保每个线程的ThreadLocal变量都是各自独立的;
  2. ThreadLocal适合在一个线程的处理流程中保持上下文(避免了同一参数在所有方法中传递);
  3. 使用ThreadLocal要用try … finally结构,并在finally中清除。

详细链接:https://www.liaoxuefeng.com/wiki/1252599548343744/1306581251653666

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

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

相关文章

【廖雪峰官方网站/Java教程】Maven基础

Maven是一个Java项目管理和构建工具&#xff0c;它可以定义项目结构、项目依赖&#xff0c;并使用统一的方式进行自动化构建&#xff0c;是Java项目不可缺少的工具。 1.Maven介绍 1.1.Maven功能及项目结构 1.1.1.Maven主要功能 Maven就是是专门为Java项目打造的管理和构建工…

【廖雪峰官方网站/Java教程】设计模式(一)

0.概述.设计模式的基本概念及原则 设计模式&#xff0c;即Design Patterns&#xff0c;是指在软件设计中&#xff0c;被反复使用的一种代码设计经验。使用设计模式的目的是为了可重用代码&#xff0c;提高代码的可扩展性和可维护性。 为什么要使用设计模式&#xff1f;根本原因…

[转]上海新东方vs新东方,SEO实战

引用前言&#xff1a;半夜无聊上网&#xff0c;看到了这篇文章&#xff0c;觉得还不错&#xff0c;看了收获不小&#xff0c;所以就转过来了。来源 非常郑重地声明一下&#xff08;文章发表约20小时后补充&#xff09; 我不得不承认&#xff0c;这篇文章有点“软”&#xff0c;…

前端web:响应式网站开发的现状你了解吗?

当企业对网络营销有了更深的认识时&#xff0c;不管是大企业还是小企业&#xff0c;都已经建立了自己的个性化响应网站&#xff0c;都希望利用互联网这一新形态的市场&#xff0c;以及网络营销的新型营销模式。如今建立响应式网站也是一种趋势&#xff0c;利用互联网的优势&…

学用MVC4做网站五:5.4删除文章

前几天把添加、修改功能都做了&#xff0c;今天开始写删除功能。删除文章既要删除文章本身同时也要在公共模型中删除对应项。 首先写从数据库中删除文章的函数。打开ArticleRepository修改Delete的函数。有上次的教训这次明白了传递的id应该是公共模型id。 /// <summary>…

三分钟免费搞定网站在线客服,利用PowerTalkBox控件制作而成,为大家提供比较好的示例...

下载地址:http://download.csdn.net/source/1876659 必须安装.net2.0才可以支持网站服务端 内带完整的安装流程,支持飞信功能,使您不在电脑前时也可以用手机交流. 可以利用以下的js代码实现浮动窗口的功能. <script languagejavascript>var cao_x,cao_y; function cao888…

amazon s3_在Amazon S3上托管静态网站

amazon s3Static website hosting on Amazon S3 is one of the very popular use cases of Amazon S3. It allows you to host an entire static website and on very low cost. Amazon S3 is a highly available and scalable hosting solution.Amazon S3上的静态网站托管是Am…

一个可以实时查相关电子产品价格的网站

香港价格网&#xff0c;里面的价格和香港的百老汇、丰泽等的价格几乎同步&#xff0c;相差不大&#xff0c;有很大的参考价值&#xff0c;对于准备去香港买电子产品的网友来说&#xff0c;是个非常好的网站&#xff0c;特别分享&#xff1a; http://www.price.com.hk/转载于:ht…

简单高效!25个漂亮的简约风格网站设计作品

在过去几年里&#xff0c;网站设计领域发生了巨大变化。除了 RWD&#xff08;响应式网页设计&#xff09;和 Web 字体的革命&#xff0c;现代设计的发展趋势迅速流行扁平化的配色方案&#xff0c;网页排版变得更加重要&#xff0c;重点已放在内容第一。最后&#xff0c;页面加载…

增城seo搜索引擎优化_搜索引擎seo优化主要从哪里入手?

首先我们应该了解什么是搜索引擎优化以及网站搜索引擎seo优化的价值&#xff0c;从基础开始逐步深入&#xff0c;下面拓王朝所要讲的都是一些理论知识&#xff0c;很好理解&#xff0c;有不同见解欢迎评论。SEO优化SEO搜索引擎优化&#xff0c;是指通过采用易于搜索引擎索引和排…

[转]使用ThinkPHP框架快速开发网站(多图)

本文转自&#xff1a;http://blog.csdn.net/ruby97/article/details/7574851 这一周一直忙于做实验室的网站&#xff0c;基本功能算是完成了。比较有收获的是大概了解了ThinkPHP框架。写一些东西留作纪念吧。如果对于同样是Web方面新手的你有一丝丝帮助&#xff0c;那就更好了挖…

《大型网站技术架构》读书笔记[3] - 架构核心五要素

架构设计中要考虑的核心五要素&#xff1b; 性能、可用性、扩展性、伸缩性、安全性 性能 性能的测试指标 响应时间 应用执行一个操作需要的时间&#xff0c;包括从发出请求开始到收到最后响应数据所需要的时间。响应时间是系统最重要的性能指标&#xff0c;直观地反映了系统的“…

java抓取网页数据_Golang丨Java丨Python爬虫实战—Boss直聘网站数据抓取

我们分别通过Golang、Python、Java三门语言&#xff0c;分别实现对Boss直聘网站的招聘数据进行爬取。首先打开Boss直聘网站&#xff1a;然后我们在职位类型中输入Go或者Golang关键字&#xff1a;然后我们可以看到一个列表&#xff0c;和Go语言相关的各种招聘职位&#xff0c;还…

我的网站被黑了,关键词被劫持,总结一下是怎么解决的。

1、发现被黑&#xff0c;网站被黑的症状 两年前自己用wordpress搭了一个网站&#xff0c;平时没事写写文章玩玩。但是前些日子&#xff0c;突然发现网站的流量突然变小&#xff0c;site了一下百度收录&#xff0c;发现出了大问题&#xff0c;网站被黑了。大多数百度抓取收录的页…

一个大图切成几个小图加载速度更快_谷歌SEO页面速度的重要性

什么是页面速度&#xff1f;页面速度是指网页加载所需的时间。一个页面的加载速度是由几个不同的因素决定的&#xff0c;包括网站的服务器、页面文件大小和图片压缩。也就是说&#xff0c;"页面速度 "并不像 "网页速度 "那么重要。"页面速度 "并…

大学计算机思维导图_3款免费在线思维导图网站,你一定要收藏一个!

1&#xff1a;迅捷画图https://www.liuchengtu.com/迅捷画图是一个专业的思维导图、流程图制作网站。支持在线创作流程图、思维导图、组织结构、ER图、网络拓扑图、UML图等等。接下来说说特色&#xff1a;l 支持导出多种格式&#xff0c;如JPG、PNG、PDF文件、txt文本等格式l 提…

python中data.find_all爬取网站为空列表_利用Golang快速爬取盗版网站的整套音频

01前言最近因为 Zigma 帮我写了个推广 Catcher 小程序软文的原因&#xff0c;答应了他帮他爬了一个盗版音频网站的整套 《李淼谈奇案》 。在制作爬虫脚本的过程中&#xff0c;也是遇到了一些有趣的问题&#xff0c;所以特此写了这篇 Blog 用于记录脚本的整一个实现与问题解决。…

java web 项目伪静态_【Java Web】使用URLRewrite实现网站伪静态

大部分搜索引擎都会优先考虑收录静态的HTML页面&#xff0c;而不是动态的*.jsp、*.php页面。但实际上绝大部分网站都是动态的&#xff0c;不可能全部是静态的HTML页面&#xff0c;因此互联网上大部分网站都会考虑伪静态——就是将*.jsp、*.php这种动态URL伪装成静态的HTML页面。…

网站html静态化 教程,新云CMS网站内容管理系统生成HTML静态化教程

网站静态化一直是SEO重点关注对象。静态化有好有坏&#xff0c;最大的好处是收录迅速&#xff0c;坏处是纯静态的HTML页面难以维护&#xff0c;特别是对于大型的网站。本文将介绍如何将新云CMS网站管理系统静态化。html本文以新云CMS 3.0为例。动画1.进入后台控制面板&#xff…

ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(四) 添加表情、群聊功能...

休息了两天&#xff0c;还是决定把这个尾巴给收了。本篇是最后一篇&#xff0c;也算是草草收尾吧。今天要加上表情功能和群聊。基本上就差不多了&#xff0c;其他功能&#xff0c;读者可以自行扩展或者优化。至于我写的代码方面&#xff0c;自己也没去重构。好的&#xff0c;我…