Java并发编程实战——结构化并发应用程序
文章目录
- 6 任务执行
- 6.1 在线程中执行任务
- 6.1.1 串行地执行任务
- 6.1.2 显式地为任务创建线程
- 6.1.3 无限制创建线程的不足
- 6.2 Executor框架
- 6.2.1 示例:基于Executor的Web服务器
- 6.2.2 执行策略
- 6.2.3 线程池
- 6.2.4 Executor的生命周期
- 6.2.5 延迟任务与周期任务
- 6.3 找出可利用的并行性
- 6.3.1 示例:串行的页面渲染器
- 6.3.2 携带结果的任务Callable和Future
- 6.3.3 示例:使用Future实现页面渲染器
- 6.3.4 在异构任务并行化中存在的局限
- 6.3.5 CompletionService:Executor与BlockingQueue
- 6.3.6 使用CompletionService实现页面渲染器
- 6.3.7 为任务设置时间
- 6.3.8 示例:旅行预订门户网站
6 任务执行
6.1 在线程中执行任务
应选择清晰的任务边界以及明确的任务执行策略。
一种自然的任务边界选择方式:以独立的客户请求为边界。
6.1.1 串行地执行任务
6.1.2 显式地为任务创建线程
程序6-1-2-1 在Web服务器中为每一个请求启动一个新的线程(不要这么做)
class ThreadPerTaskWebServer {public static void main(String[] args) throws IOException {ServerSocket socket = new ServerSocket(80);while (true) {final Socket connection = socket.accept();Runnable task = new Runnable() {@Overridepublic void run() {handleRequest(connection);}};new Thread(task).start();}}
}
串行情况下可以提升性能。只要请求的到达速率不超出服务器的请求处理能力。
6.1.3 无限制创建线程的不足
线程生命周期的开销非常高。 线程创建需要时间,延迟处理的请求,并且需要JVM和操作系统提供一些辅助操作。
资源消耗。 活跃线程会消耗系统资源,尤其是内存。大量空闲的线程会占用许多内存,给垃圾收集器带来压力,而且大量线程在竞争CPU资源时还会产生其他的性能开销。如果有足够多的线程使所有的CPU保持忙碌状态,那么再创建更多的线程反而会降低性能。
稳定性。 在一定范围内,增加线程可以提高系统的吞吐率,但是如果超出了这个范围,再创建更多的线程会降低程序的执行速度。如果过多地创建一个线程,那么整个应用程序将崩溃。
6.2 Executor框架
Executor基于生产者-消费者模式,提交任务的操作相当于生产者(生成待完成的工作单元),执行任务的线程相当于消费者(执行完这些工作单元)。
6.2.1 示例:基于Executor的Web服务器
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;class TaskExecutionWebServer {private static final int NTHREADS = 100;private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);public static void main(String[] args) throws IOException {ServerSocket socket = new ServerSocket(80);while (true) {final Socket connection = socket.accept();Runnable task = new Runnable() {@Overridepublic void run() {handleRequest(connection);}};exec.execute(task);}}
}
6.2.2 执行策略
使用线程池来取代new Thread方式。
6.2.3 线程池
- newFixedThreadPool。固定长度的线程池。
- newCachedThreadPool。如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程;而当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制。
- newSingleThreadExecutor。创建单个工作线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。
- newScheduledThreadPool。固定长度的线程池。以延迟或者定时的方式来执行任务。
6.2.4 Executor的生命周期
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;public class LifeCycleWebServer {private final ExecutorService exec = ...;public void start() throws IOException {ServerSocket socket = new ServerSocket(80);while (!exec.isShutdown()) {try {final Socket conn = socket.accept();exec.execute(new Runnable() {@Overridepublic void run() {handleRequest(conn);}});} catch (RejectedExecutionException e) {if (!exec.isShutdown()) {log("task submission rejected", e);}}}}public void stop() {exec.shutdown();}void handleRequest(Socket connection) {Request req = readRequest(connection);if (isShutdownRequest(req)) {stop();} else {dispatchRequest(req);}}
}
6.2.5 延迟任务与周期任务
推荐使用ScheduleThreadPoolExecutor。
Timer存在的问题:
(1)Timer支持基于绝对时间的调度机制,因此任务的执行对系统时钟变化很敏感。ScheduleThreadPoolExecutor只支持基于相对时间的变化。
(2)Timer执行所有的定时任务时只会创建一个线程。如果某个任务的执行时间过长,那么将破坏其他TimerTask的定时精确性。例如某个周期TimerTask需要每10ms执行一次,而另一个需要40ms,那么这个周期任务或者在40ms之后快速连续调用四次,或者彻底丢失4次调用。
(3)Timer线程并不捕获异常。如果TimerTask抛出异常终止了定时任务,timer不会回复线程执行,而是会错误的认为整个Timer都被取消了。 因此,已被调度但尚未执行的TimerTask将不会再执行,新的任务也不会被调度。
程序清单 6-2-5-1 错误的Timer行为
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;public class OutOfTime {public static void main(String[] args) throws Exception {long startTime = System.currentTimeMillis();Timer timer = new Timer();timer.schedule(new ThrowTask(), 1);TimeUnit.SECONDS.sleep(1);long endTime = System.currentTimeMillis();System.out.println(endTime - startTime);timer.schedule(new ThrowTask(), 1);TimeUnit.SECONDS.sleep(5);}static class ThrowTask extends TimerTask {@Overridepublic void run() {throw new RuntimeException();}}
}Exception in thread "Timer-0" java.lang.RuntimeExceptionat executor.OutOfTime$ThrowTask.run(OutOfTime.java:22)at java.base/java.util.TimerThread.mainLoop(Timer.java:566)at java.base/java.util.TimerThread.run(Timer.java:516)
1002
Exception in thread "main" java.lang.IllegalStateException: Timer already cancelled.at java.base/java.util.Timer.sched(Timer.java:409)at java.base/java.util.Timer.schedule(Timer.java:205)at executor.OutOfTime.main(OutOfTime.java:15)
程序一秒钟就结束了,并抛出了异常。
在Java5.0或者更高的JDK中,将很少使用Timer。
6.3 找出可利用的并行性
6.3.1 示例:串行的页面渲染器
import java.util.ArrayList;
import java.util.List;public class SingleThreadRender {void renderPage(CharSequence source) {renderText(source);List<ImageData> imageData = new ArrayList<>();for (ImageInfo imageInfo : scanForImageInfo(source)) {imageData.add(imageInfo.downloadImage());}for (ImageData data : imageData) {renderImage(data);}}
}
6.3.2 携带结果的任务Callable和Future
6.3.3 示例:使用Future实现页面渲染器
程序清单6-3-3-1 使用Future等待图像下载
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;public class FutureRender {private final ExecutorService executor = ...;void renderPage(CharSequence source) {final List<ImageInfo> imageInfos = scanForImageInfo(source);Callable<List<ImageData>> task = new Callable<List<ImageData>>() {@Overridepublic List<ImageData> call() throws Exception {List<ImageData> result = new ArrayList<>();for (ImageInfo imageInfo : imageInfos) {result.add(imageInfo.downloadImage());}return result;}};Future<List<ImageData>> future = executor.submit(task);renderText(source);try {List<ImageData> imageDataList = future.get();for (ImageData imageData : imageDataList) {renderImage(imageData);}} catch (InterruptedException e) {// 重新设置线程的中断状态Thread.currentThread().interrupt();// 由于不需要结果,因此取消任务future.cancel(true);} catch (ExecutionException e) {throw launcherThrowable(e.getCause());}}}
6.3.4 在异构任务并行化中存在的局限
只有大量相互独立且同构的任务可以并发进行处理时,才能体现出将程序的工作负载分配到多个任务中带来的真正性能提升。
6.3.5 CompletionService:Executor与BlockingQueue
6.3.6 使用CompletionService实现页面渲染器
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;public class Render {private final ExecutorService excutor;Render(ExecutorService excutor) {this.excutor = excutor;}void renderPage(CharSequence source) {List<ImageInfo> info = scanForImageInfo(source);CompletionService<ImageData> completionService = new ExecutorCompletionService<>(excutor);for (final ImageInfo imageInfo: info) {completionService.submit(new Callable<ImageData>() {@Overridepublic ImageData call() throws Exception {return imageInfo.downloadImage();}});}renderText(source);try {for (int t = 0, n = info.size(); t < n; t++) {Future<ImageData> f = completionService.take();ImageData imageData = f.get();renderImage(imageData);}} catch (InterruptedException e) {Thread.currentThread().interrupt();} catch (ExecutionException e) {throw launcherThrowable(e.getCause());}}
}
6.3.7 为任务设置时间
Page renderPageWithAd() throws InterruptedException {long endNanos = System.nanoTime() + END_BUDGET;Future<Ad> f = excutor.submit(new FetchAdTask());// 在等待广告的同时显示页面。Page page = renderPageBody();Ad ad;try {long timeLeft = endNanos - System.nanoTime();ad = f.get(timeLeft, TimeUnit.NANOSECONDS);} catch (ExecutionException e) {ad = DEFAULT_AD;} catch (TimeoutException e) {ad = DEFAULT_AD;f.cancel(true);}page.setAd(ad);return page;}
6.3.8 示例:旅行预订门户网站
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;private class QuoteTask implements Callable<TravelQuote> {private final TravelCompany company;private final TravelInfo travelInfo;@Overridepublic TravelQuote call() throws Exception {return company.solicitQuote(travelInfo);}
}public List<TravelQuote> getRankedTravelQuotes(TravelInfo travelInfo, Set<TravelCompany> companies,Comparator<TravelQuote> ranking, long time, TimeUnit unit) throws InterruptedException {List<QuoteTask> tasks = new ArrayList<>();for (TravelCompany company : companies) {tasks.add(new QuoteTask(company, travelInfo));}List<Future<TravelQuote>> futures = exec.invokeAll(tasks, time, unit);List<TravelQuote> quotes = new ArrayList<>(futures.size());Iterator<QuoteTask> iterator = tasks.iterator();for (Future<TravelQuote> f : futures) {QuoteTask task = iterator.next();try {quotes.add(f.get());} catch (ExecutionException e) {quotes.add(task.getFailureQuote(e.getCause()));} catch (CancellationException e) {quotes.add(task.getTimeoutQuote(e.getCause()));}}Collections.sort(quotes, ranking);return quotes;
}
相关文章:
Java并发编程实战——结构化并发应用程序
文章目录 6 任务执行6.1 在线程中执行任务6.1.1 串行地执行任务6.1.2 显式地为任务创建线程6.1.3 无限制创建线程的不足 6.2 Executor框架6.2.1 示例:基于Executor的Web服务器6.2.2 执行策略6.2.3 线程池6.2.4 Executor的生命周期6.2.5 延迟任务与周期任务 6.3 找出…...
uniapp echarts 点击失效
这个问题网上搜了一堆,有的让你降版本,有的让你改源码。。。都不太符合预期,目前我的方法可以用最新的echarts。 这个方法就是由npm安装转为CDN,当然你可能会质疑用CDN这样会不稳定,那如果CDN的地址是本地呢࿱…...
手机开启应急预警通知 / 地震预警
前言 安卓手机在检测到地震时,将发送地震预警通知,但此设置是默认关闭的,原因是以防引发用户恐慌从而引发安全问题,且开启此设置需要完成指引教程,因此默认关闭此设置。下文介绍如何开启此设置。 开启方法 华为手机开…...
2020年12月 Python(一级)真题解析#中国电子学会#全国青少年软件编程等级考试
一、单选题(共25题,每题2分,共50分) 第1题 执行语句print(10==10.0)的结果为? A:10 B:10.0 C:True D:False 正确的答案是 C:True。 解析:在Python中,比较运算符 “==” 用于比较两个值是否相等。在这个特定的比较中,整数10和浮点数10.0在数值上是相等的。…...
遇到无法复现的 Bug
当我们在软件开发过程中遇到无法复现的 Bug 时,这可能会让我们感到头疼和困惑。处理这种 Bug 需要一些技巧和方法来帮助我们更好地解决问题。本篇博客将为大家总结一些常用的技术手段和策略,希望能对开发者们在日常工作中遇到类似问题时提供一些帮助。 …...
虚拟列表的实现(简单易懂)
起因: app开发过程中遇到需要渲染3000行的列表,页面直接卡顿,所以开始研究起虚拟列表 实现前提条件: item项等高列表 实现思路: 首先是dom结构: 定义一个容器(固定高度)&#…...
【WordPress】如何在WordPress中实现真·页面路由
这篇文章也可以在我的博客中查看 页面路由 是什么 页面路由是指从url顺着网线砍到网站内容的途径,说人话就是地址与页面的映射。 就像真实世界的地址一样,我要找你,必须知道你的地址。 在网站中,通过地址找内容的机制…...
Android界面设计与用户体验
Android界面设计与用户体验 1. 引言 在如今竞争激烈的移动应用市场,提供优秀的用户体验成为了应用开发的关键要素。无论应用功能多么强大,如果用户界面设计不合理,用户体验不佳,很可能会导致用户流失。因此,在Androi…...
基于 FFmpeg 的跨平台视频播放器简明教程(八):音画同步
系列文章目录 基于 FFmpeg 的跨平台视频播放器简明教程(一):FFMPEG Conan 环境集成基于 FFmpeg 的跨平台视频播放器简明教程(二):基础知识和解封装(demux)基于 FFmpeg 的跨平台视频…...
【NLP pytorch】基于BiLSTM-CRF模型医疗数据实体识别实战(项目详解)
基于BiLSTM-CRF模型医疗数据实体识别实战 1数据来源与加载1.1 数据来源1.2 数据类别名称和定义1.3 数据介绍2 模型介绍2 数据预处理2.1 数据读取2.2 数据标注2.3 数据集划分2.4 词表和标签的生成3 Dataset和DataLoader3.1 Dataset3.2 DataLoader4 BiLSTM模型定义5 CRF模型6 模型…...
人工智能原理(1)
*请注意,本文仅供学习使用* 目录 一、人工智能发展 1、孕育期 2、摇篮期 3、形成期 4、发展期(1970-1979) 5、实用期 6、稳步发展期 二、何为人工智能 1、智能的主要观点 2、智能定义 3、人工智能定义 三、人工智能研究方法 1、…...
预测成真,国内传来三个消息,中国年轻人变了,创新力产品崛起
中国的年轻人真的变了! 最近,国内传来三个消息,让外媒的预测成真。 第一,奥迪要开始用国产车的平台了。这里需要说明的是新能源汽车,奥迪也曾多次公开表示,承认了当前中国新能源汽车核心技术上的领先。 第…...
维深(Wellsenn):2023中国消费端VR内容开发商调研报告(附下载
关于报告的所有内容,公众【营销人星球】获取下载查看 核心观点 国内互联网大厂商入局VR,字节跳动、网易表态明确。字节跳动2021年收购国内头部VR硬件厂商PICO后,加速构建VR内容生态,2021年 成立海南创见未来当前已推出VR视频应用…...
redis事务管理详解
事务管理 事务管理乐观锁与悲观锁watch命令实现乐观锁watch命令示例 事务管理 Redis 提供了事务管理功能,可以通过 Redis 的 MULTI、EXEC、WATCH 和 DISCARD 命令来实现。 开启事务: 使用 MULTI 命令开始一个事务,表示接下来执行的命令都属于…...
国产低功耗蓝牙HS6621CxC/6621Px系列支持Find My网络功能方案芯片
目录 什么是“Find My“?HS6621系列简介 什么是“Find My“? “Find My”是苹果公司于19年前推出的针对失物追踪,Find My iPhone(查找我的iPhone)和Find My Friends(查找朋友)的结合体应用。为…...
【openGauss】分区表的介绍与使用
一、openGauss分区表介绍 在openGauss中,数据分区是在一个节点内部对数据按照用户指定的策略做进一步的水平分表,将表中的数据按照指定方式划分为多个互不重叠的部分。 对于大多数用户使用场景,分区表和普通表相比具有以下优点: …...
代码随想录算法训练营day57
文章目录 Day57回文子串题目思路代码 最长回文子序列题目思路代码 Day57 回文子串 647. 回文子串 - 力扣(LeetCode) 题目 给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。…...
【基础类】—前后端通信类系统性学习
一、什么是同源策略及限制 同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。源:协议、域名和端口, 默认端口是80 三者有一个不同,即源不同,就是跨域 ht…...
vite项目中使用@代表根路径
1.配置vite.config.ts import { defineConfig } from vite import vue from vitejs/plugin-vue import path from pathexport default defineConfig({plugins: [vue()],resolve: {alias:{: path.resolve(__dirname, src) }} })2.报错path和__dirname 找不到模块“path”或其相…...
冶金化工操作VR虚拟仿真实验软件提高员工们协同作业的配合度
对于高风险行业来说,开展安全教育培训是企业的重点工作,传统培训逐渐跟不上时代变化和工人需求,冶金安全VR模拟仿真培训系统作为一种新型的教育和培训工具,借助VR虚拟现实技术为冶金行业的工人提供一个安全、高效的培训环境。 冶金…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...
SpringAI实战:ChatModel智能对话全解
一、引言:Spring AI 与 Chat Model 的核心价值 🚀 在 Java 生态中集成大模型能力,Spring AI 提供了高效的解决方案 🤖。其中 Chat Model 作为核心交互组件,通过标准化接口简化了与大语言模型(LLM࿰…...
【深度学习新浪潮】什么是credit assignment problem?
Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...
鸿蒙Navigation路由导航-基本使用介绍
1. Navigation介绍 Navigation组件是路由导航的根视图容器,一般作为Page页面的根容器使用,其内部默认包含了标题栏、内容区和工具栏,其中内容区默认首页显示导航内容(Navigation的子组件)或非首页显示(Nav…...
Springboot 高校报修与互助平台小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,高校报修与互助平台小程序被用户普遍使用,为…...
