Java多线程与线程池技术详解(十)

拥有梦想,即拥有了生命的火种。

梦想是一座高山,攀爬起来虽然艰辛,但一旦到达顶峰,你的努力就将被铭记于人心。

梦想是一个拼图,每一次努力都是一块拼图,最终汇成一个完整的梦想。

梦想是你的信念,即使远离了人群,它仍然激励你前进。

只有持续不断地前进,才能迎接你成就梦想的那一刻。


目录

上一篇博客习题讲解

实现自己的ThreadPoolExecutor,并测试其行为

自定义线程池

测试不同场景下的线程池行为

修改TaskQueue的行为,使其在某些条件下拒绝接受新任务

使用AsyncContext创建一个异步Servlet应用

探索Tomcat源码中关于NIO的具体实现细节

第10章 并发编程应用

10.1 JVM与多线程

10.2 Servlet与多线程

10.3 懒汉与饿汉模式

10.4 数据库Connection 与多线程

10.4.1 ThreadLocal与线程私有数据

10.4.2 ThreadLocal 存储数据库 Connection

10.4.3 ThreadLocal 实现Connection per logic 模式

10.4.4 ThreadLocal 实现Connection per request 模式

10.5 高并发网站的PageView统计

10.6 生成唯一的订单号

10.7 浏览器并发请求限制

10.8 NIO与多路复用

10.9 远程异步访问

10.10 防止缓存雪崩的DCL机制

10.11 分布式锁解决商品超卖


上一篇博客习题讲解

Java多线程与线程池技术详解(九)

Java多线程与线程池技术详解(九)-CSDN博客文章浏览阅读1.1k次,点赞29次,收藏13次。Tomcat的线程池是基于Java的实现的,但为了适应Web服务器的需求,它做了许多定制化处理。在创建自定义的时,可以指定核心线程数(corePoolSize)、最大线程数(maximumPoolSize)、空闲线程存活时间(keepAliveTime)、任务队列(workQueue)等参数。Tomcat中的与标准JDK版本不同,它增加了对提交任务计数的支持,并且在执行任务失败时会尝试将任务重新加入到任务队列中。// 自定义ThreadPoolExecutor构造函数// 预热所有核心线程。https://blog.csdn.net/speaking_me/article/details/144418617?spm=1001.2014.3001.5502

实现自己的ThreadPoolExecutor,并测试其行为

为了实现一个自定义的ThreadPoolExecutor,我们将创建一个继承自ThreadPoolExecutor的类,并重写一些方法来添加暂停/恢复机制。此外,我们还将探索不同的拒绝策略,如AbortPolicyCallerRunsPolicy等,并根据应用场景选择最合适的策略。

自定义线程池

首先,我们需要构建一个自定义线程池,这包括设置核心线程数(corePoolSize)、最大线程数(maximumPoolSize)、保持活动时间(keepAliveTime)、工作队列(workQueue)和拒绝策略(handler)。这里我们以PausableThreadPoolExecutor为例,它允许我们在运行时暂停和恢复线程池中的任务执行。

import java.util.concurrent.*;public class PausableThreadPoolExecutor extends ThreadPoolExecutor {private final ReentrantLock lock = new ReentrantLock();private final Condition condition = lock.newCondition();private volatile boolean isPaused = false;public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}@Overrideprotected void beforeExecute(Thread t, Runnable r) {super.beforeExecute(t, r);lock.lock();try {while (isPaused) {try {condition.await(); // Wait until the thread pool is resumed.} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("Thread was interrupted", e);}}} finally {lock.unlock();}}public void pause() {lock.lock();try {isPaused = true;} finally {lock.unlock();}}public void resume() {lock.lock();try {isPaused = false;condition.signalAll(); // Notify all waiting threads to proceed.} finally {lock.unlock();}}
}
测试不同场景下的线程池行为

接下来,我们要编写单元测试用例来验证线程池的各种特性是否按预期工作。例如,我们可以测试高负载情况下的表现,以及当线程池满时的任务处理方式。通过这些测试,可以确保我们的自定义线程池在各种条件下都能稳定运行。

import org.junit.Test;
import static org.junit.Assert.*;public class PausableThreadPoolExecutorTest {@Testpublic void testPauseAndResume() throws InterruptedException {PausableThreadPoolExecutor executor = new PausableThreadPoolExecutor(2, 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());// Submit tasks that will be paused and resumed.for (int i = 0; i < 5; i++) {final int taskId = i;executor.submit(() -> {System.out.println("Task " + taskId + " started.");try {Thread.sleep(1000); // Simulate work.} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("Task " + taskId + " finished.");});}// Pause the executor.executor.pause();Thread.sleep(500); // Allow time for tasks to potentially start.// Ensure no more tasks are running after pausing.assertTrue(executor.getQueue().size() > 0);// Resume the executor.executor.resume();Thread.sleep(2000); // Allow enough time for all tasks to complete.// Check if all tasks have been processed.assertEquals(0, executor.getQueue().size());}
}
修改TaskQueue的行为,使其在某些条件下拒绝接受新任务

对于修改任务队列的行为,使之能够在特定条件下拒绝接收新任务,可以通过自定义阻塞队列或者直接修改线程池的提交逻辑来实现。下面的例子展示了如何基于系统负载决定是否接受新任务:

import java.util.concurrent.*;public class LoadSensitiveBlockingQueue<T> extends LinkedBlockingQueue<T> {private final int maxLoad;private AtomicInteger currentLoad = new AtomicInteger(0);public LoadSensitiveBlockingQueue(int capacity, int maxLoad) {super(capacity);this.maxLoad = maxLoad;}@Overridepublic boolean offer(T e) {if (currentLoad.get() >= maxLoad) {System.out.println("The system is under heavy load, rejecting new tasks.");return false; // Reject new tasks when the system is under heavy load.} else {currentLoad.incrementAndGet();return super.offer(e);}}@Overridepublic T poll(long timeout, TimeUnit unit) throws InterruptedException {T task = super.poll(timeout, unit);if (task != null) {currentLoad.decrementAndGet();}return task;}
}
使用AsyncContext创建一个异步Servlet应用

创建一个使用AsyncContext API 的异步Servlet应用可以帮助提高服务器资源利用率和服务响应速度。即使是在处理长时间运行的操作时,也不会阻塞主线程,从而改善用户体验。

下面是一个简单的例子:

@WebServlet(urlPatterns = "/asyncServlet", asyncSupported = true)
public class AsyncServletExample extends HttpServlet {protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {final AsyncContext asyncContext = request.startAsync();asyncContext.setTimeout(10 * 60 * 1000); // Set timeout to 10 minutes.asyncContext.start(() -> {try {// Simulate a long-running operation.Thread.sleep(5000); // Sleep for 5 seconds.PrintWriter out = asyncContext.getResponse().getWriter();out.write("This message was generated asynchronously.");out.flush();asyncContext.complete();} catch (InterruptedException | IOException e) {e.printStackTrace();}});}
}
探索Tomcat源码中关于NIO的具体实现细节

最后,深入研究Tomcat源码中与NIO相关的部分,特别是从6.x版本开始支持的非阻塞I/O模型。了解Tomcat是如何利用Java NIO特性(如SelectorChannelBuffer)来管理大量并发连接的,可以帮助我们更好地理解现代Web服务器的工作原理。要开始这个过程,可以从下载或克隆Tomcat项目的GitHub仓库做起,然后关注配置文件server.xml中的Connector节点,尤其是那些指定了协议为org.apache.coyote.http11.Http11NioProtocol的地方。接着,查看NioEndpoint组件的工作流程,包括AcceptorPollerSocketProcessor等角色的作用。注意Tomcat是如何处理连接限制、事件轮询及任务分配的,这些都是保证高效并发处理的基础。

第10章 并发编程应用

10.1 JVM与多线程

Java虚拟机(JVM)为Java应用程序提供了运行时环境,它不仅负责解释字节码、管理内存,还支持多线程编程。在Java中,每个线程都是操作系统级别的实体,并且每个Java线程都会映射到一个操作系统的原生线程。因此,线程的创建、销毁和调度都由操作系统负责。JVM通过其内置的线程管理机制支持多线程编程,使得编写多线程程序变得相对容易,因为JVM提供了丰富的工具来帮助开发者处理并发问题。

代码示例:

// 创建一个新的线程并启动它。
Thread thread = new Thread(() -> {// 线程执行体System.out.println("Thread is running.");
});
thread.start(); // 启动线程
10.2 Servlet与多线程

Servlet容器默认采用单实例多线程的方式来处理请求。当Web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并实例化(只存在一个Servlet实例);当请求到达时,Servlet容器通过调度线程池中等待执行的线程给请求者;线程执行Servlet的service()方法;请求结束,放回线程池,等待被调用。这种模式提高了请求的响应时间,但同时也要求开发者特别注意线程安全问题,例如避免使用实例变量(成员变量),因为如果存在成员变量,可能发生多线程同时访问该资源时,导致数据的不一致。

代码示例:

@WebServlet("/example")
public class ExampleServlet extends HttpServlet {private static final long serialVersionUID = 1L;@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 处理GET请求response.getWriter().println("Handling GET request.");}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 处理POST请求response.getWriter().println("Handling POST request.");}
}
10.3 懒汉与饿汉模式

懒汉模式指的是在第一次调用getInstance()方法时才创建单例对象,而饿汉模式则是在类加载时就创建了单例对象。懒汉模式可以延迟加载,节省内存资源,但在多线程环境下需要考虑线程安全问题,通常会使用双重检查锁定(Double-Checked Locking, DCL)模式来确保线程安全。而饿汉模式简单直接,但由于实例在类加载时就已经创建,因此不能实现延迟加载。

代码示例:

懒汉模式:

public class LazySingleton {private static volatile LazySingleton instance = null;private LazySingleton() {}public static LazySingleton getInstance() {if (instance == null) { // 第一次检查synchronized (LazySingleton.class) {if (instance == null) { // 第二次检查instance = new LazySingleton();}}}return instance;}
}

饿汉模式:

public class HungrySingleton {private static final HungrySingleton INSTANCE = new HungrySingleton();private HungrySingleton() {}public static HungrySingleton getInstance() {return INSTANCE;}
}
10.4 数据库Connection 与多线程
10.4.1 ThreadLocal与线程私有数据

ThreadLocal为每个线程提供了一个独立的变量副本,使得每个线程都可以独立地访问自己的副本而不受其他线程的影响。这为解决多线程环境下的共享资源问题提供了一种有效的解决方案。

10.4.2 ThreadLocal 存储数据库 Connection

为了确保每个线程都有自己独立的数据库连接,可以通过ThreadLocal来存储每个线程的Connection对象。这样即使在同一应用程序的不同线程之间也可以保持各自的数据库会话隔离,从而避免了多线程共享同一个Connection所带来的潜在问题。

代码示例:

public class DatabaseHelper {private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();public static Connection getConnection() throws SQLException {Connection conn = connectionHolder.get();if (conn == null) {conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "user", "password");connectionHolder.set(conn);}return conn;}public static void closeConnection() {Connection conn = connectionHolder.get();if (conn != null) {try {conn.close();} catch (SQLException e) {// handle exception} finally {connectionHolder.remove();}}}
}
10.4.3 ThreadLocal 实现Connection per logic 模式

在这种模式下,对于每个业务逻辑单元(如事务边界内的所有操作),都会分配一个新的Connection,并通过ThreadLocal保证该Connection仅限于当前逻辑单元内使用。一旦逻辑单元完成,即关闭此Connection,以释放资源。

10.4.4 ThreadLocal 实现Connection per request 模式

对于Web应用而言,每次HTTP请求都应该有自己的数据库连接。利用ThreadLocal可以在每次请求开始时创建一个新的Connection,并在请求结束时关闭它,确保每个请求都有独立的数据库会话。

代码示例:

@WebServlet("/dbOperation")
public class DbOperationServlet extends HttpServlet {private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {Connection conn = null;try {conn = DatabaseHelper.getConnection();// 执行数据库操作...} catch (SQLException e) {// 处理异常...} finally {DatabaseHelper.closeConnection();}}
}
10.5 高并发网站的PageView统计

在高并发场景下,准确统计页面浏览量(PageView)是一个挑战。一种常见的做法是引入消息队列或缓存层来缓冲实时计数,并定期将这些增量更新同步到持久化存储中。此外,还可以采用分布式锁或其他一致性协议来防止竞态条件的发生。

10.6 生成唯一的订单号

为了确保订单号的唯一性,可以结合时间戳、机器ID以及序列号来构造订单号。比如,可以使用UUID或者基于雪花算法(Snowflake Algorithm)生成全局唯一的ID。这种方法能够有效地避免重复订单号的问题。

代码示例:

public class OrderIdGenerator {private static final AtomicLong sequence = new AtomicLong(0);public static String generateOrderId() {return String.format("%s-%d", UUID.randomUUID().toString(), sequence.incrementAndGet());}
}
10.7 浏览器并发请求限制

现代浏览器对同一域名下的并发请求数量有一定的限制,通常是6个左右。超出这个数量的请求会被排队等待直到有空闲的连接可用。开发者应该了解这一点,并优化前端代码以减少不必要的请求,提高用户体验。

10.8 NIO与多路复用

NIO(New I/O)提供了非阻塞I/O的支持,允许一个线程处理多个网络连接。通过选择器(Selector)机制,可以监听多个通道(Channel)上的事件,如读写就绪等,从而实现高效的并发处理。这种方式特别适合构建高性能的服务端应用。

10.9 远程异步访问

远程异步访问是指客户端发起请求后不需要立即等待响应,而是继续执行后续任务,待服务端完成处理后再通知客户端结果。这种方式可以显著提升系统的响应速度和服务能力。实现上可以通过回调函数、Future/Promise模式或者消息队列等方式达成。

10.10 防止缓存雪崩的DCL机制

缓存雪崩指的是大量缓存同时失效,导致瞬间大量的请求直接打到数据库上,造成系统压力骤增。为了避免这种情况,可以在缓存设置时加入随机的时间偏移量,分散缓存过期时间;同时,在获取缓存时采用双重检查锁定(Double-Checked Locking, DCL)模式,确保只有一个线程负责加载新数据。

10.11 分布式锁解决商品超卖

分布式环境中,多个节点可能同时尝试修改相同的资源,如库存数量,这就可能导致商品超卖等问题。使用分布式锁可以在多个服务实例间协调对共享资源的访问,确保同一时刻只有一个实例能够进行更新操作。常见的分布式锁实现包括Redis、Zookeeper等。

以上内容涵盖了从基础概念到高级话题的广泛领域,旨在帮助初学者全面理解并发编程的核心思想和技术细节。每个部分都包含了理论知识和实践指导。

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

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

相关文章

后端-redis在springboot项目中的使用步骤

redis在springboot项目中的使用场景 如果再创建一张包含状态的表&#xff0c;里面就有两个字段一个id&#xff0c;一个状态&#xff0c;太浪费&#xff0c;那就使用redis存储&#xff0c; 设置营业状态打烊还是营业中

【鸿蒙实战开发】数据的下拉刷新与上拉加载

本章介绍 本章主要介绍 ArkUI 开发中最常用的场景下拉刷新, 上拉加载&#xff0c;在本章中介绍的内容在实际开发过程当中会高频的使用,所以同学们要牢记本章的内容。下面就让我们开始今天的讲解吧&#xff01; List 组件 在 ArkUI 中List容器组件也可以实现数据滚动的效果&a…

ElasticSearch 常见故障解析与修复秘籍

文章目录 一、ElasticSearch启动服务提示无法使用root用户二、ElasticSearch启动提示进程可拥有的虚拟内存少三、ElasticSearch提示用户拥有的可创建文件描述符太少四、ElasticSearch集群yellow状态分析五、ElasticSearch节点磁盘使用率过高&#xff0c;read_only状态问题解决六…

Motionface RTASR 离线实时语音识别直播字幕使用教程

软件使用场景&#xff1a; 直播、视频会议、课堂教学等需要实时字幕的场景。 1&#xff1a;系统要求 软件运行支持32位/64位windows 10/11系统&#xff0c;其他硬件要求无&#xff0c;无显卡也能实时识别字幕。 2&#xff1a;下载安装 链接:百度网盘 请输入提取码 提取码&#…

Https身份鉴权(小迪网络安全笔记~

附&#xff1a;完整笔记目录~ ps&#xff1a;本人小白&#xff0c;笔记均在个人理解基础上整理&#xff0c;若有错误欢迎指正&#xff01; 5.2 Https&身份鉴权 引子&#xff1a;上一篇主要对Http数据包结构、内容做了介绍&#xff0c;本篇则聊聊Https、身份鉴权等技术。 …

Linux 中的 mkdir 命令:深入解析

在 Linux 系统中&#xff0c;mkdir 命令用于创建目录。它是文件系统管理中最基础的命令之一&#xff0c;广泛应用于日常操作和系统管理中。本文将深入探讨 mkdir 命令的功能、使用场景、高级技巧&#xff0c;并结合 GNU Coreutils 的源码进行详细分析。 1. mkdir 命令的基本用法…

【实验】【H3CNE邓方鸣】交换机端口安全实验+2024.12.11

实验来源&#xff1a;邓方鸣交换机端口安全实验 软件下载&#xff1a; 华三虚拟实验室: 华三虚拟实验室下载 wireshark&#xff1a;wireshark SecureCRT v8.7 版本: CRT下载分享与破解 文章目录 dot1x 开启802.1X身份验证 开启802.1X身份验证&#xff0c;需要在系统视图和接口视…

OpenCV实验篇:识别图片颜色并绘制轮廓

第三篇&#xff1a;识别图片颜色并绘制轮廓 1. 实验原理 颜色识别的原理&#xff1a; 颜色在图像处理中通常使用 HSV 空间来表示。 HSV 空间是基于人类视觉系统的一种颜色模型&#xff0c;其中&#xff1a; H&#xff08;Hue&#xff09;&#xff1a;色调&#xff0c;表示颜色…

vue2-请求代理,动态target

当你在 Vue 2 项目中将 axios 的 baseURL 配置为 http://192.168.11.111:8762 时&#xff0c;所有请求都被认为是绝对路径请求&#xff0c;这种请求会直接发送到目标服务器&#xff0c; 跳过开发服务器的代理。 baseURL具体值 这就是为什么代理配置无法拦截 /exportPdf 的原因…

算法-字符串-76.最小覆盖子串

一、题目 二、思路解析 1.思路&#xff1a; 滑动窗口&#xff01;&#xff01;&#xff01; 2.常用方法&#xff1a; 无 3.核心逻辑&#xff1a; 1.特殊情况&#xff1a;s或t是否为空字符串 if(snull||tnull)return ""; 2.声明一个字符数组——用于记录对应字符出现…

BatchNorm 与 LayerNorm

文章目录 1. BatchNorm批量归一化2. LayerNorm层归一化3. BatchNorm 和 LayerNorm 对比4. BatchNorm 和 LayerNorm 怎么选择References 今天重看Transformer&#xff0c;发现里面提到了BatchNorm和LayerNorm两种归一化方法&#xff0c;在这儿做一下总结和整理。 1. BatchNorm批…

《机器学习》2.4假设检验 t分布 F分布

目录 t发布 注意是这个东西服从t分布 数据服从t分布通常是在以下情况下&#xff1a; 以下是一些具体的例子&#xff0c;说明在何种情况下数据会服从t分布&#xff1a; t检验 交叉验证t检验 样本方差​编辑 F分布&#xff08;fisher Friedman检验是一种非参数统计方法&a…

java aspose word 模板根据数据导出pdf

支持以功能&#xff1a; 1、字符串占位符替换。 2、占位符循环替换。 3、图片替换。 4、基础图标&#xff0c;折现、饼图、柱状图。 本案例运行环境&#xff1a; 1、aspose word21.1版本。 2、jdk 18。 话不多说直接上代码。 <!-- 图表相关 --><dependency><gro…

Linux高性能服务器编程 | 读书笔记 |9.定时器

9. 定时器 网络程序需要处理定时事件&#xff0c;如定期检测一个客户连接的活动状态。服务器程序通常管理着众多定时事件&#xff0c;有效地组织这些定时事件&#xff0c;使其在预期的时间被触发且不影响服务器的主要逻辑&#xff0c;对于服务器的性能有至关重要的影响。为此&…

QT 国际化(翻译)

QT国际化&#xff08;Internationalization&#xff0c;简称I18N&#xff09;是指将一个软件应用程序的界面、文本、日期、数字等元素转化为不同的语言和文化习惯的过程。这使得软件能够在不同的国家和地区使用&#xff0c;并且可以根据用户的语言和地区提供本地化的使用体验。…

3D 生成重建034-NerfDiff借助扩散模型直接生成nerf

3D 生成重建034-NerfDiff借助扩散模型直接生成nerf 文章目录 0 论文工作1 论文方法2 实验结果 0 论文工作 感觉这个论文可能能shapE差不多同时期工作&#xff0c;但是shapE是生成任意种类。 本文提出了一种新颖的单图像视图合成方法NerfDiff&#xff0c;该方法利用神经辐射场 …

【CNN卷积神经网络算法】卷积神经网络

卷积神经网络整体架构 总结 输入层&#xff1a;原始数据&#xff08;图像&#xff09; 例如可以输入一张RGB图像&#xff0c;其可以标示为一个三维矩阵&#xff0c;宽W高H和通道数C3对应着RGB卷积层&#xff1a;提取局部特征&#xff0c;变换输入数据为丰富的特征图 网络使用多…

HarmonyOS Next 元服务新建到上架全流程

HarmonyOS Next 元服务新建到上架全流程 接上篇 这篇文章的主要目的是介绍元服务从新建到上家的完整流程 在AGC平台上新建一个项目 链接 一个项目可以多个应用 AGC新建一个元服务应用 新建一个本地元服务项目 如果成功在AGC平台上新建过元服务&#xff0c;那么这里会自动显…

Mac/Windows端长期破解myBase8方法(无需安装火绒)

提醒 不管哪个端&#xff0c;都需要先退出myBase。 Mac 进入用户根目录/Users/c0ny100&#xff0c;即下边是Macintosh HD > 用户 > [你的用户名]这个界面然后按ShiftCommond.&#xff0c;显示隐藏文件。找到.Mybase8.ini文件 打开.Mybase8.ini文件&#xff0c;删除Fir…

【网络安全】【Kali Linux】简单ICMP主机探测

一、参考资料 《Python安全攻防——渗透测试实战指南》&#xff0c;吴涛 等编著&#xff0c;机械工业出版社&#xff0c;2021年10月 二、探测原理 ICMP&#xff08;ping命令&#xff09; 三、脚本编写 1、导入所需的库&#xff1a; 2、扫描功能函数&#xff1a; 3、主函数…