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

news2024/11/29 3:44:40

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

同步是多个线程之间按照一定的顺序进行执行,比如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.coloradmin.cn/o/1348681.html

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

相关文章

Django 学习教程- Hello world入门案例

系列 Django 学习教程-介绍与安装-CSDN博客 欢迎来到第Djagno学习教程第二章Hello World 入门案例。 在本教程中&#xff0c;我将引导您完成django的Hello World入门案例。 让我们开始吧&#xff01; 版本 Django 5.0Python 3.10 创建项目 安装 Django 之后&#xff0…

Linux 权限掌控术:深入探索和用户管理

文章目录 前言1.外壳程序是什么&#xff1f;外壳程为什么存在&#xff1f;工作原理外壳程序怎么个事&#xff1f; 2. Linux权限的概念2.1 什么是权限2.2权限的本质2.3 Linux中的用户 3. 普通用户变成rootlinux中有三种人 4.Linux中文件的权限4.1文件的属性权限4.2 掌握修改权限…

[DL]深度学习_AlexNet

AlexNet网络详解 目录 一、AlexNet 1、详细介绍 2、网络框架 二、网络详解 1、首次使用ReLu激活函数 2、模型基本结构与双GPU实现 3、局部响应归一化(LRN) 4、重叠池化(Overlapping Pooling) 5、数据增强 6、Dropout 一、AlexNet 1、详细介绍 AlexNet是一种经典的卷积…

Javascript 循环结构while do while for实例讲解

Javascript 循环结构while do while for实例讲解 目录 Javascript 循环结构while do while for实例讲解 一、while语句 二、do…while语句 三、for循环 疑难解答 我们从上一节课知道&#xff0c;JavaScript循环结构总有3种&#xff1a; &#xff08;1&#xff09;while语…

S7.Net与西门子PLC通讯

开发环境准备 同时由于设备还未采购,没有设备进行测试开发,会安装一个仿真模拟器进行调试。(真心的,谁想到还能仿真模拟设备) 需要安装的软件包括 ◦TIA Portal V14 ◦S7-PLCSIM V14 ◦NetToPLCsim-S7o-v-1-2-2-0 TIA Portal 西门子官方管理设备数据块的客户端,每一…

【网络安全】upload靶场pass11-17思路

目录 Pass-11 Pass-12 Pass-13 Pass-14 Pass-15 Pass-16 Pass-17 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x…

盾构机数据可视化监控平台 | 图扑数字孪生

2002 年,中国 863 计划把盾构机列为国家关键技术&#xff0c;以国家力量为主导&#xff0c;集中力量进行盾构机专项研究。在 2008 年&#xff0c;中国成功研制出属于自己的国产盾构机——中国中铁一号&#xff0c;同时还打通了天津地铁 1500m 的隧道。此举更彻底地打破了国内盾…

基于Java SSM框架实现企业销售人员培训系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现企业销售人员培训系统演示 摘要 目前由于我国企业销售人员培训系统的规模较小,同发达国家相比,人员培训比重始终偏低,用户意识比较低下,为进一步提高用户的销售意意识的提升,我觉得开发一套关于企业销售人员培训系统很人必要,虽然许多企业主动选用人员培…

测试新字符设备驱动代码

一. 简介 上一篇文章编写了新字符设备驱动框架的代码&#xff0c;并编写了 LED灯IO初始化工作&#xff0c;也编写了Led的开与关函数。文章地址如下&#xff1a; 向新字符设备驱动代码框架中添加Led功能函数-CSDN博客 本文对所完成的新字符设备驱动代码进行测试。通过测试程…

[电磁学]大学物理陈秉乾老师课程笔记

主页有博主其他上万字的精品笔记,都在不断完善ing~ 第一讲 绪论,库仑定律 主要讲解了电磁学中的库伦定律和电场的相关概念&#xff0c;介绍了电荷和电磁相互作用的规律&#xff0c;并讲解了电场强度和电势的概念。 03:14 &#x1f393; 库伦定律&#xff1a;电势能与电荷的关…

学习华为企业无线网络,有这篇文章就够了。

知识改变命运&#xff0c;技术就是要分享&#xff0c;有问题随时联系&#xff0c;免费答疑&#xff0c;欢迎联系&#xff01; •以有线电缆或光纤作为传输介质的有线局域网应用广泛&#xff0c;但有线传输介质的铺设成本高&#xff0c;位置固定&#xff0c;移动性差。随着人们对…

智安网络|实现安全与网络功能一体化:SASE的全新安全策略

随着企业信息化和数字化程度的不断提升&#xff0c;网络安全面临着前所未有的挑战。传统的网络安全模式已经无法满足日益复杂的安全需求。在这一背景下&#xff0c;安全访问服务边缘&#xff08;SASE&#xff09;崭露头角&#xff0c;并逐渐成为新一代网络安全架构的关键概念。…

丢失VCRUNTIME140_1.dll怎么办,多种dll问题解决方法分享

丢失VCRUNTIME140_1.dll是许多计算机用户经常遇到的问题之一。VCRUNTIME140_1.dll是一个动态链接库文件&#xff0c;它是Visual C Redistributable Package的一部分。Visual C Redistributable Package是微软为了支持运行使用Visual C编写的软件而提供的一个可再发行组件包。当…

SpringCloud-高级篇(十)

&#xff08;1&#xff09;单节点Redis问题 缓存大家都不陌生&#xff0c;在企业里面只要做缓存都会用到Redis&#xff0c;我们在使用的时候都是做的单节点部署&#xff0c;单节点部署是存在一些问题的&#xff0c;分布式缓存正是Redis的集群&#xff0c;正是为了解决单节点部署…

Linux调试工具—gdb

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;HEART BEAT—YOASOBI 2:20━━━━━━️&#x1f49f;──────── 5:35 &#x1f504; ◀️ ⏸ ▶️ ☰ …

Django 学习教程- Django模板(Template)

系列 Django 学习教程-介绍与安装-CSDN博客 Django 学习教程- Hello world入门案例-CSDN博客 前言 在上一章节中我们使用django.http.HttpResponse() 来输出 "Hello World&#xff01;"。该方式将数据与视图混合在一起&#xff0c;不符合 Django 的 MTV 思想。 本…

【Java进阶篇】JDK新版本中的新特性都有哪些

JDK新版本中的新特性都有哪些 ✔️经典解析✔️拓展知识仓✔️本地变量类型推断✔️Switch 表达式✔️Text Blocks✔️Records✔️封装类✔️instanceof 模式匹配✔️switch 模式匹配 ✅✔️虚拟线程 ✔️经典解析 JDK 8中推出了Lambda表达式、Stream、Optional、新的日期API等…

c++_08_操作符重载(操作符重定义) 友元

1 操作符标记 单目操作符&#xff1a; - -- * -> 等 双目操作符&#xff1a; - > < - << >> 等 三木操作符&#xff1a; ? : 2 操作符函数 2.0 前言 C编译器有能力把一个由操作…

Ubuntu之修改时区/时间

1、查看当前时间及时区状态 sudo timedatectl status # 显示当前时区为Asia/Shanghai 2、查看当前系统时间 sudo date 3、查看当前系统时间及时区 sudo date -R # 显示当前时间及对应时区&#xff0c;时区为“0800”北京时区 4、修改硬件时间 修改日期格式&#xff1a…

论虚继承的作用

虚继承 实验介绍 在上一小节中学习了多继承与多重继承,实际在开发的时候可能会遇到一种情况,既用到了多继承又用到了多重继承,这种继承方式通常又称为菱形继承。但这样一来就会产生新的问题,过多消耗空间。希望通过本小节学习能知道菱形继承以及产生的问题和解决方式。 …