【并发设计模式】聊聊Thread-Per-Message与Worker-Thread模式

在并发编程中,核心就是同步、互斥、分工。

同步是多个线程之间按照一定的顺序进行执行,比如A执行完,B在执行。而互斥是多个线程之间对于共享资源的互斥。两个侧重点不一样,同步关注的是执行顺序,互斥关注的是资源的排他性访问。

分工则是从一个宏观的角度,将任务庖丁解牛,将一个大任务进行拆分,每个线程执行一个任务的子集。主要的设计模式就是Thread-Per-Message(来一个任务,新建一个线程执行)、Worker-Thread(复用线程池)、生产者 - 消费者模式。本篇我们先介绍前两个。

  • Thread-Per-Message 模式需要注意线程的创建,销毁以及是否会导致OOM。
  • Worker Thread 模式需要注意死锁问题,提交的任务之间不要有依赖性。
  • 生产者 - 消费者模式可以直接使用线程池来实现

生活场景触发

我们来想一个实际的参观厨师、服务员的方式。如果来一个客人,再去招聘人,然后开始做饭,显然销量不高,所以就会提前雇佣好一批人,来了客人直接做饭。但是显然也有饭店火爆的情况,那么就让客人先在休息区等待,等待进行排号吃饭。因为目前饭店已经达到的上限。对比其实就是上述的三种方式。

Thread-Per-Message

这种方式比较好理解,针对于每个客户端的请求,来一个请求就新建一个Thread进行处理。
但是显而易见,这种方式新建线程、销毁线程的操作是很耗时,比较浪费资源。并且如果大量的线程处理任务耗时比较久,那么就会出现OOM,所以JUC中就提供了线程中方式,根据需要配置线程池进行处理任务。
在GO语言中有更加轻量级的协程,以及java中Loom 推荐你可以看看。

解决方案:短期可以增大JVM内存配置,调整大新生代大小,长期解决NIO或者AIO等

Loom

Project Loom is to intended to explore, incubate and deliver Java VM features and APIs built on top of them for the purpose of supporting easy-to-use, high-throughput lightweight concurrency and new programming models on the Java platform.
This OpenJDK project is sponsored by the HotSpot Group.

Code


/*** @author qxlx* @date 2023/12/31 10:33 PM*/
public class ServerTest {public static void main(String[] args) throws IOException {final ServerSocketChannel ssc = ServerSocketChannel.open().bind(new InetSocketAddress(8088));while (true) {SocketChannel sc = ssc.accept();new Thread(() -> {ByteBuffer rb = ByteBuffer.allocate(1024);try {sc.read(rb);TimeUnit.SECONDS.sleep(2000);System.out.println(Thread.currentThread().getName() + " 接收的数据:" + rb.toString());ByteBuffer wb = (ByteBuffer) rb.flip();sc.write(wb);sc.close();} catch (Exception e) {e.printStackTrace();}}, "Thread-" + Math.random()).start();}}}

Worker-Thread

Woker-Thread 其实就是我提前雇佣一批工人,等待干活,来活就干,没活就休息。可以避免频繁的创建线程。
在这里插入图片描述
Worker Thread 模式能避免线程频繁创建、销毁的问题,而且能够限制线程的最大数量。
Java 语言里可以直接使用线程池来实现 Worker Thread 模式,线程池是一个非常基础和优秀 的工具类,甚至有些大厂的编码规范都不允许用 new Thread() 来创建线程,必须使用线程池。

Code

public static void main(String[] args) throws IOException {ExecutorService threadPoolExecutor = new ThreadPoolExecutor(10,20,30000,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));final ServerSocketChannel ssc = ServerSocketChannel.open().bind(new InetSocketAddress(8088));while (true) {SocketChannel sc = ssc.accept();threadPoolExecutor.execute(() -> {ByteBuffer rb = ByteBuffer.allocate(1024);try {sc.read(rb);TimeUnit.SECONDS.sleep(2000);System.out.println(Thread.currentThread().getName() + " 接收的数据:" + rb.toString());ByteBuffer wb = (ByteBuffer) rb.flip();sc.write(wb);sc.close();} catch (Exception e) {e.printStackTrace();}});}}

避免死锁

在实际的开发中,使用线程池需要注意任务之间是否有依赖关系,否则有以来关系的话,可能会引起线程死锁。

在这里插入图片描述
如下就是2个线程,执行的时候,因为线程池线程使用完毕,本来需要4个,但是只有两个,另外两个线程任务执行不了,所以就死锁了。

package com.jia.dp;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** @author qxlx* @date 2024/1/1 11:17 AM*/
public class ThreadPoolDeadLockTest {public static void main(String[] args) throws InterruptedException {ExecutorService threadPool = Executors.newFixedThreadPool(2);CountDownLatch l1 = new CountDownLatch(2);System.out.println("l1-begin");// 大任务l1 2个for (int i = 0; i < 2; i++) {threadPool.execute(()-> {CountDownLatch l2 = new CountDownLatch(2);System.out.println("l2-begin");//小任务l2 2个for (int j = 0; j < 2; j++) {threadPool.execute(()->{l2.countDown();});}try {l2.await();l1.countDown();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("l2-end");});}System.out.println("l1-end");l1.await();}}
"Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007f8d4480a800 nid=0x3223 in Object.wait() [0x000000030646a000]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x0000000715586bf8> (a java.lang.ref.Reference$Lock)at java.lang.Object.wait(Object.java:502)at java.lang.ref.Reference.tryHandlePending(Reference.java:191)- locked <0x0000000715586bf8> (a java.lang.ref.Reference$Lock)at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)"main" #1 prio=5 os_prio=31 tid=0x00007f8d56010800 nid=0x1903 waiting on condition [0x0000000305949000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for  <0x00000007156ac720> (a java.util.concurrent.CountDownLatch$Sync)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231)at com.jia.dp.ThreadPoolDeadLockTest.main(ThreadPoolDeadLockTest.java:40)

在这里插入图片描述
可以发现是因为l1.await() 阻塞,l1阻塞的原因就是l2线程任务没有执行完毕,l2线程任务没有线程资源可以处理任务,所以就是死锁了。

解决方案
1.调整线程池的大小,更加方便的是,使用不同的线程池任务进行处理不同的任务。

总结

本篇介绍了两种分工协作的方式,一种是来一个任务new一个线程处理,另外一种就是通过线程池进行达到线程的复用。实际生产中是采用后者的方式。

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

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

相关文章

CGAL的空间排序

1、介绍 许多在CGAL中实现的几何算法都是增量的&#xff0c;因此它们的速度取决于插入顺序。此软件包提供了排序算法&#xff0c;可以大大提高此类算法的运行时间。 其基本原理是沿着空间填充曲线对对象进行排序&#xff0c;这样在插入顺序上&#xff0c;几何上接近的两个对象将…

.net8 AOT编绎-跨平台调用C#类库的新方法-函数导出

VB.NET AOT无法编绎DLL,微软的无能&#xff0c;正是你的机会 .net8 AOT编绎-跨平台调用C#类库的新方法-函数导出 1&#xff0c;C#命令行创建工程&#xff1a;dotnet new classlib -o CSharpDllExport 2&#xff0c;编写一个静态方法&#xff0c;并且为它打上UnmanagedCallersO…

Linux - 设置虚拟机和主机IP在同一网段(桥接)

1.查看主机ip地址等相关信息。 ipconfig -all 2.设置虚拟网络编辑器 打开虚拟网络编辑器 设置虚拟网络编辑器&#xff0c;设置为桥接模式。&#xff08;记得以管理员方式打开VMware&#xff09;。 3.修改虚拟机网卡文件 查看虚拟机ip,我们的目标是将其修改为与主机同一网段…

postman使用-04响应

文章目录 响应响应界面说明Pretty&#xff1a;格式化显示&#xff0c;以便查看Raw&#xff1a;不进行任何处理&#xff0c;显示响应数据的原始格式Preview&#xff1a;预览响应体&#xff0c;会自动换行&#xff0c;不会格式化&#xff08;有时候是数据&#xff0c;有时候是页面…

什么是缓存、为什么要用缓存、缓存分类、缓存测试、缓存更新、缓存设计考虑点、缓存测试点

一、缓存 缓存是一种将数据存储在高速缓存中的技术&#xff0c;它可以提高应用程序的性能和响应速度。 二、 为什么要用缓存 1. 高性能(主要目的) 查询耗时&#xff0c;但变化少&#xff0c;又有很多读请求情况下&#xff0c;可以将查询结果放到缓存中。减少对数据库的压力&…

Java位运算及移位运算

java中能表示整数数据类型的有byte、short、char、int、long&#xff0c;在计算机中占用的空间使用字节描述&#xff0c;1个字节使用8位二进制表示。 数据类型字节数二进制位数表示范围默认值byte18-27 – 27-10char2160 – 216-1\u0000 (代表字符为空 转成int就是0)short216-…

Word 将页面方向更改为横向或纵向

文章目录 更改整个文档的方向更改部分页面的方向方法1&#xff1a;方法2&#xff1a; 参考链接 更改整个文档的方向 选择“布局”>“方向”&#xff0c;选择“纵向”或“横向”。 更改部分页面的方向 需要达到下图结果&#xff1a; 方法1&#xff1a; 选:中你要在横向页面…

帆软报表中定时调度中的最后一步如何增加新的处理方式

在定时调度中,到调度执行完之后,我们可能想做一些别的事情,当自带的处理方式不满足时,可以自定义自己的处理方式。 产品的处理方式一共有如下这些类型: 我们想在除了上面的处理方式之外增加自己的处理方式应该怎么做呢? 先看下效果: 涉及到两方面的改造,前端与后端。…

C++日期类的实现

前言&#xff1a;在类和对象比较熟悉的情况下&#xff0c;我们我们就可以开始制作日期表了&#xff0c;实现日期类所包含的知识点有构造函数&#xff0c;析构函数&#xff0c;函数重载&#xff0c;拷贝构造函数&#xff0c;运算符重载&#xff0c;const成员函数 1.日期类的加减…

【产品设计】信息建设三驾马车:PLM系统拆解

本篇文章将介绍PLM的基础信息、发展及模块功能等内容&#xff0c;让大家对PLM有一个全面、完整地了解&#xff0c;方便在后期的工作中能快速地使用其解决方案&#xff0c;希望本篇文章能对你有所帮助。 PLM系统主要实现产品模块业务&#xff0c;既包含产品的创意设计、样品打样…

【源码】-MyBatis-如何系统地看源码

写在前面 前段时间做过一个项目&#xff0c;期间用到了动态数据源dynamic-datasource&#xff0c;经历了dbcp2的数据库连接池没有生效到排查定位、MyBatis多种数据库产品兼容、手写MyBatis拦截器等事情。 花费了好久&#xff0c;一直在打磨这篇文章&#xff08;不知道花费这么长…

thinkphp6.0升级到8.0

目录 一&#xff1a;升级过程 二&#xff1a;报错处理 最近写的项目需要使用thinkphp8.0&#xff0c;之前的老项目需要从php6.0升级到8.0&#xff0c;特此记录下升级过程。 一&#xff1a;升级过程 查看版本&#xff1a; php think version,我目前的版本是6.1.4 生成thin…

WPF+Halcon 培训项目实战(8-9):WPF+Halcon初次开发

文章目录 前言相关链接项目专栏运行环境匹配图片WPF Halcon组件HSmartWindowControlWPF绑定读取图片运行代码运行结果 抖动问题解决运行结果 绘制矩形绘制图像会消失 绘制对象绑定事件拖动事件 前言 为了更好地去学习WPFHalcon&#xff0c;我决定去报个班学一下。原因无非是想…

C/C++面向对象(OOP)编程-回调函数详解(回调函数、C/C++异步回调、函数指针)

本文主要介绍回调函数的使用&#xff0c;包括函数指针、异步回调编程、主要通过详细的例子来指导在异步编程和事件编程中如何使用回调函数来实现。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;C/C精进之路 &…

Spark魔力:招聘网站数据深度分析系统

Spark魔力&#xff1a;招聘网站数据深度分析系统 简介数据集技术栈功能特点创新点 简介 在本文中&#xff0c;我们将介绍一款基于Spark的招聘网站数据分析系统&#xff0c;该系统使用爬取的前程无忧招聘数据。通过结合Flask、Pandas、PySpark、以及MySQL等技术&#xff0c;实现…

【汇编笔记】初识汇编-内存读写

汇编语言的由来&#xff1a; CPU是计算机的核心&#xff0c;由于计算机只认识二进制&#xff0c;所以CPU执行的指令是二进制。 我们要想让CPU工作&#xff0c;就得给他提供它认识的指令&#xff0c;这一系列的指令的集合&#xff0c;称之为指令集。 指令集&#xff1a; 不同的体…

单片机键盘程序设计举例

1、键盘与的连接 图3键盘连接 图4单片机与键盘接口图 2、通过1/0口连接。将每个按钮的一端接到单片机的I/O口&#xff0c;另一端接地&#xff0c;这是最简单的办法&#xff0c;如图3所示是实验板上按钮的接法&#xff0c;四个按钮分别接到P3.2 、P3.3、P3.4和P3.5。对于这种键…

css 设置鼠标覆盖显示菜单

鼠标覆盖到“全部分类”效果如下 鼠标放到“精品推荐”效果如下 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"&g…

【CF比赛记录】—— Good Bye 2023(A、B、C)

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;CF比赛记录 &#x1f48c;其他专栏&#xff1a; &#x1f534;每日一题 &#x1f7e1; cf闯关练习 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓…

mysql基础-表操作

环境&#xff1a; 管理工具&#xff1a;Navicat 数据库版本&#xff1a;5.7.37 mysql的版本&#xff0c;我们可以通过函数&#xff0c;version()进行查看&#xff0c;本次使用的版本如下&#xff1a; 目录 1.管理工具 1.1创建表 1.2.修改表名 1.3.复制表 1.4.删除表 2…