Java学习小记——多线程Socket编程

news2024/10/5 14:18:20

目录

  • 线程池
    • 线程池介绍
    • 线程池的参数
  • Java线程池ExecutorTester
  • 服务器socket编程
    • 普通socket编程
    • 线程池并行处理客户请求
    • Java NIO异步处理客户请求

线程池

线程池介绍

在创建一个线程时存在一定的开销,创建线程的开销相比于一般的算法来说较大。首先需要建立一个调用栈,接着操作系统还需要建立很多数据结构来维护线程的状态等等。为了解决每次都需要临时创建线程带来开销的问题,引入了线程池。

线程池会预先建立好线程,等待任务派发。一个线程池内可能会有几十上百个闲置的线程,当有任务来临时,需要选中一个线程执行任务。执行完毕后释放该线程资源,重新回到闲置状态,等待下一个任务。

在线程池中,通常有一个Blocking Queue用来派发任务。队列通常为空,当有新的任务进队列时,闲置线程开始抢占该任务。当没有闲置线程时,新任务到达Blocking Queue便会开始排队,等待有线程空出来。

线程池的参数

corePoolSize:线程池中初始线程的数量,可能处于等待状态
maimumPoolSize:线程池中最大允许线程数量
keepAliveTime:超出corePoolSize部分线程如果等待这些时间,就会被回收

Java线程池ExecutorTester

可以使用ThreadPoolExecutor创建线程池:
在这里插入图片描述
参数包含了corePoolSize,maimumPoolSize,keepAliveTime,时间单元TimeUnit以及一个可执行的workQueue任务队列。

另一种写法是用executors.newFixedThreadPool()创建线程池。这样的写法参数较少,只需要指定一个线程数即可。

ExecutorService executor = Executors.newFixedThreadPool(3);

如此创建的线程池,拥有的线程数量固定为3,如果任务数大于3,那么多余的任务则只有排队等待。

接下来我们一口气给这个线程池派发10个任务试试:

for (int i = 0; i < 10; i++) {
	executor.submit(new CodingTask(i));
}
System.out.println("10 tasks dispatched successfully.");

在这里插入图片描述
由于线程池参数为3,所以会按3个一组来抢占任务。
在这里插入图片描述
第一轮派发完毕后:
在这里插入图片描述
可以看到将3个任务派发给了线程们,workQueue中还剩7个任务。

Future<?>可以用来控制任务的执行:
在这里插入图片描述
在这里插入图片描述
cancel(boolean):可以临时中止执行任务
get():若成功执行则返回null,否则等待
iscancelled():获取任务是否中止
isDone():获取任务是否成功执行

ExecutorService executor = Executors.newFixedThreadPool(3);

    List<Future<?>> taskResults = new LinkedList<>();
    for (int i = 0; i < 10; i++) {
      taskResults.add(executor.submit(new CodingTask(i)));
    }
    System.out.println("10 tasks dispatched successfully.");

    for (Future<?> taskResult : taskResults) {
      taskResult.get();//get等待task结束,一旦结束会返回null
    }

服务器socket编程

普通socket编程

我们设想这样一个场景,一个服务器监听在6666端口上。若客户发aaa,则响应Hello aaa。首先我们需要建立一个服务端socket去监听端口号:

ServerSocket serverSocket = new ServerSocket(6666)

然后建立一个客户端socket,用getRemoteSocketAddress()方法获取远端socket端口号:

Socket clientSocket = serverSocket.accept();
System.out.println("Incoming connection from "
            + clientSocket.getRemoteSocketAddress());

最后用clientSoclet提供的getInputStream()方法和getOutputStream()方法实现开头我们所设想的功能:

try(ServerSocket serverSocket = new ServerSocket(6666)){
			System.out.println("listening on "+serverSocket.getLocalSocketAddress());
			Socket clientSocket = serverSocket.accept();
			System.out.println("Incoming connection from "+clientSocket.getRemoteSocketAddress());
			try(Scanner input = new Scanner(clientSocket.getInputStream())){
				String request = input.nextLine();
				System.out.println(String.format("Request from %s: %s",
						clientSocket.getRemoteSocketAddress(),
						request));
				String response = "Hello"+request+".\n";
				clientSocket.getOutputStream().write(response.getBytes());
			}
		}

执行以上代码并在命令行执行:

telnet localhost 6666

在这里插入图片描述
输入aaa后,服务端返回消息:
在这里插入图片描述
在这里插入图片描述
当然,正常的服务器不会在处理完一个请求后就马上切断与客户端的连接。所以,我们需要在把处理请求块放进一个循环中,并且加入终止条件即可。

try(ServerSocket serverSocket = new ServerSocket(6666)){
			System.out.println("listening on "+serverSocket.getLocalSocketAddress());
			Socket clientSocket = serverSocket.accept();
			System.out.println("Incoming connection from "+clientSocket.getRemoteSocketAddress());
			try(Scanner input = new Scanner(clientSocket.getInputStream())){
				while(true) {
					String request = input.nextLine();
					if(request.equals("quit")) {
						break;
					}
					System.out.println(String.format("Request from %s: %s",
							clientSocket.getRemoteSocketAddress(),
							request));
					String response = "Hello "+request+".\n";
					clientSocket.getOutputStream().write(response.getBytes());
				}
			}
		}

在这里插入图片描述
但是此时的服务器只能处理一个客户的请求。当另开一个控制台telnet 6666端口时,由于已经被占用,其他客户无法接受服务。

线程池并行处理客户请求

线程池在前面已经介绍过,它预先建立好了线程,等待任务来临。在线程池中有一个Blocking Queue,任务到来后会进入该队列,当到达队头并且有闲置进程时就会被选中执行。
在这里插入图片描述

在Java中,扔进Blocking Queue的是一个个Client Handler,因为每个client会占据一个线程,所以Client Handler即代表待执行的任务。线程调用Client Handle的run()方法来执行该任务。

	ExecutorService executor = Executors.newFixedThreadPool(3);
    RequestHandler requestHandler = new RequestHandler();

    try (ServerSocket serverSocket = new ServerSocket(7777)) {
      System.out.println("Listening on "
          + serverSocket.getLocalSocketAddress());

      while (true) {
        Socket clientSocket = serverSocket.accept();
        System.out.println("Incoming connection from "
            + clientSocket.getRemoteSocketAddress());
        executor.submit(
            new ClientHandler(clientSocket, requestHandler));
      }
    }

在这里插入图片描述

此时,服务器端就就可以支持多线程工作,因为线程池最大为3,所以最多可以同时开3个命令行访问服务器。当第4个命令行请求服务时,仍可连接成功,但需等待前3个服务中至少有一个服务退出,才可以接受服务。

Java NIO异步处理客户请求

每个线程处理一个客户请求看似十分合理,实则也存在缺点。因为线程池的容量有限,一个几十上百线程的线程池最多也只能服务几十上百个客户,难以实现大容量高吞吐的服务端。

在上一节的服务端线程池中,看似是所有线程都在忙着应付手头上的任务,而无暇顾及新到的任务,然而事实真的如此吗?比如当线程等待用户的输入时,用户的输入可以非常慢,这时线程便会被阻塞,并非在忙碌工作着。

因此,我们是否可以将所有client的input记住,再去检查谁输入完毕,就先去读这个输入并处理。也就是说,我们应该把一个个的request去交给线程处理,而不是把整个client都交给线程去处理。这样就避免了整个线程的等待时间,即将同步处理优化为了异步处理,这就是NIO异步服务器。
在这里插入图片描述

在NIO异步服务器中,等待的任务不再进入一个Blocking Queue,而是进入一个Channel列表。列表中有很多的Client,在每个循环开始时,通过一个Selector选择器去选择一个有数据的Channel,接着Request Handle去处理这个有数据的Channel,如此循环。

	ServerSocketChannel serverChannel = ServerSocketChannel.open();
    serverChannel.configureBlocking(false);
    serverChannel.bind(new InetSocketAddress(8888));
    System.out.println("Listening on "
        + serverChannel.getLocalAddress());

    Selector selector = Selector.open();
    serverChannel.register(selector, SelectionKey.OP_ACCEPT);

    ByteBuffer buffer = ByteBuffer.allocate(1024);
    RequestHandler requestHandler = new RequestHandler();
    while (true) {
      int selected = selector.select();
      if (selected == 0) {
        continue;
      }

      Set<SelectionKey> selectedKeys = selector.selectedKeys();
      Iterator<SelectionKey> keyIter = selectedKeys.iterator();

      while (keyIter.hasNext()) {
        SelectionKey key = keyIter.next();

        if (key.isAcceptable()) {
          ServerSocketChannel channel =
              (ServerSocketChannel) key.channel();
          SocketChannel clientChannel = channel.accept();
          System.out.println("Incoming connection from "
              + clientChannel.getRemoteAddress());
          clientChannel.configureBlocking(false);
          clientChannel.register(
              selector, SelectionKey.OP_READ);
        }

        if (key.isReadable()) {
          SocketChannel channel =
              (SocketChannel) key.channel();
          channel.read(buffer);
          String request = new String(buffer.array()).trim();
          buffer.clear();
          System.out.println(String.format(
              "Request from %s: %s",
              channel.getRemoteAddress(),
              request));
          String response = requestHandler.handle(request);
          channel.write(ByteBuffer.wrap(response.getBytes()));
        }

        keyIter.remove();
      }
    }

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

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

相关文章

如何在Windows 10中启用或关闭Windows功能?这里有详细步骤

Windows 10是一个拥有数百种功能的大型操作系统。与任何其他操作系统或软件一样&#xff0c;大多数PC用户从未使用Windows 10中提供的所有功能。例如&#xff0c;内置的XPS Viewer对大多数PC用户来说都是陌生的&#xff0c;尽管它自Windows7推出以来就一直存在。 高级PC用户通…

mybatis 基础入门使用

1、mybatis 简介 1.1、mybatis 特性 MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架&#xff1b; MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集&#xff1b; MyBatis可以使用简单的XML或注解用于配置和原始映射&#xff0c;将接口和…

百度云AI

百度云AI概述 Face腾讯优图科大讯飞 百度人脸识别基于深度学习的人脸识别方案&#xff0c;准确识别图片中的人脸信息&#xff0c;提供如下功能&#xff1a; 人脸检测&#xff1a;精准定位图中人脸&#xff0c;获得眼、口、鼻等72个关键点位置&#xff0c;分析性别、年龄、表…

CI/CD部署

什么是CI&#xff0c;什么是CD CI和CD是软件开发中持续集成和持续交付的缩写。 CI代表持续集成&#xff08;Continuous Integration&#xff09;&#xff0c;是一种实践&#xff0c;旨在通过自动化构建、测试和代码静态分析等过程&#xff0c;频繁地将代码变更合并到共享存储…

ArcGIS API for JavaScript 4.X 本地部署(js,字体)

0 目录&#xff08;4.19&#xff09; /4.19/ 1 修改文件 1.1 init.js 编辑器打开/4.19/init.js搜索文本[HOSTNAME_AND_PATH_TO_JSAPI]&#xff0c;然后将其连同前面的https://替换为http://ip地址/4.19&#xff0c;可以是localhost&#xff0c;只能本机引用 替换后&#xff…

【软考高级信息系统项目管理师--第二十章:高级项目管理】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;软考高级–信息系统项目管理师 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; 第二十章&#xff1a;高级项目管理 项目集管理项目组合管理组织级项目管理OPM&…

AI提示工程实战:从零开始利用提示工程学习应用大语言模型【文末送书-19】

文章目录 背景什么是提示工程&#xff1f;从零开始&#xff1a;准备工作设计提示调用大语言模型 实际应用示例文字创作助手代码生成持续优化与迭代数据隐私与安全性可解释性与透明度总结 AI提示工程实战&#xff1a;从零开始利用提示工程学习应用大语言模型【文末送书-19】⛳粉…

【软考问题】-- 10 - 知识精讲 - 项目风险管理

一、基本问题 1&#xff1a;按照可预测性&#xff0c;风险分哪三类&#xff1f; &#xff08;1&#xff09;已知风险&#xff1a;如项目目标不明确&#xff0c; 过分乐观的进度计划&#xff0c; 设计或施工变更和材料价格波动等。&#xff08;2&#xff09;可预测风险&#xff…

跟着pink老师前端入门教程(JavaScript)-day01

一、计算机编程基础 &#xff08;一&#xff09;编程语言 1、编程 编程&#xff1a;就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码&#xff0c;并最终得到结果的过程。 计算机程序&#xff1a;就是计算机所执行的一系列的指令集合&#xff0c;而程序全部…

spfa的特殊用法

spfa通常用来求带有负权边的最短路问题&#xff0c;但是它还有两种特别的用法——求负环和求差分约束 求负环 我们回顾spfa算法&#xff0c;本质上是一个点的距离被更新以后再用它去更新其他的点。将被更新的点放入队列中&#xff0c;这样一直更新&#xff0c;直到没有任何点…

Stable Diffusion系列(六):原理剖析——从文字到图片的神奇魔法(潜空间篇)

文章目录 LDM概述原理模型架构自编码器模型扩散模型条件引导模型图像生成过程 实验结果指标定义IS&#xff08;越大越好&#xff09;FID&#xff08;越小越好&#xff09; 训练成本与采样质量分析不带条件的图片生成基于文本的图片生成基于语义框的图片生成基于语义图的图片生成…

金蝶云星空——用递归SQL查询物料分组

应用场景&#xff1a; 金蝶物料分组为树形结构&#xff0c;需要根据SQL查询同步到第三方系统中。 技术实现 用递归CTE按照树状结构展开物料分组 with cte as( select 0 as 物料分组层级,t1.FID,case when isnull(t1.FFULLPARENTID,) then .CAST(t1.FID AS VARCHAR(…

K8s进阶之路-核心概念/架构:

架构&#xff1a;Master/Node Master组件--主控节点{ 负责集群管理&#xff08;接收用户事件转化成任务分散到node节点上&#xff09;} Apiserver&#xff1a; 资源操作的唯一入口&#xff0c;提供认证、授权、API注册和发现等机制 Scheduler &#xff1a; 负责集群资源调度&am…

K8s进阶之路-安装部署K8s

参考&#xff1a;&#xff08;部署过程参考的下面红色字体文档链接就可以&#xff0c;步骤很详细&#xff0c;重点部分在下面做了标注&#xff09; 安装部署K8S集群文档&#xff1a; 使用kubeadm方式搭建K8S集群 GitBook 本机&#xff1a; master&#xff1a;10.0.0.13 maste…

三防工业平板丨亿道加固平板定制丨三防平板电脑丨提升后勤管理

企业的后勤管理对于运作高效的商业模式至关重要。随着科技的不断发展&#xff0c;加固平板成为提升企业后勤水平的一项关键措施。本文将探讨加固平板在企业后勤管理中的应用和优势&#xff0c;并阐述如何利用这一技术提升企业的运营效率和竞争力。 一、三防加固平板的定义和功能…

通用二进制方式安装MySQL8.0.x

一、必要说明 1、系统&#xff1a;openEuler操作系统 2、版本&#xff1a;MySQL - 8.0.36 3、下载地址&#xff1a;https://dev.mysql.com/get/Downloads/MySQL-8.0 二、安装步骤 1、下载glibc版本的Mysql [rootnode2 ~]# wget -c https://dev.mysql.com/get/Downloads/MySQ…

《乱弹篇(十六)知不言》

日本电影《我的男人》&#xff0c;是伦理情色类型片。故事讲述&#xff0c;一个小姑娘在地震后失去了亲人&#xff0c;她被远房亲戚&#xff08;一位消防员收养&#xff09;。 图&#xff1a;来源电影《我的男人》广告 两人一起生活&#xff0c;难免日久生情&#xff0c;于是便…

【Jvm】运行时数据区域(Runtime Data Area)原理及应用场景

文章目录 前言&#xff1a;Jvm 整体组成 一.JDK的内存区域变迁Java8虚拟机启动参数 二.堆0.堆的概念1.堆的内存分区2.堆与GC2.1.堆的分代结构2.2.堆的分代GC2.3.堆的GC案例2.4.堆垃圾回收方式 3.什么是内存泄露4.堆栈的区别5.堆、方法区 和 栈的关系 三.虚拟机栈0.虚拟机栈概念…

Redis系列学习文章分享---第一篇(Redis快速入门之初始Redis--NoSql+安装redis+客户端+常用命令)

目录 今天开始进入Redis系列学习分享1.初识Redis1.1.认识NoSQL1.1.1.结构化与非结构化1.1.2.关联和非关联1.1.3.查询方式1.1.4.事务1.1.5.总结 1.2.认识Redis1.3.安装Redis1.3.1.依赖库1.3.2.上传安装包并解压1.3.3.启动1.3.4.默认启动1.3.5.指定配置启动1.3.6.开机自启 1.4.Re…

防御保护--防火墙综合实验

防御保护--防火墙综合实验 一、实验需求二、实验配置1.配置IP地址及划分安全区域2.FW2和FW4组成主备模式的双机热备2.1 检查双机热备状态 3.DMZ区存在两台服务器&#xff0c;现在要求生产区的设备仅能在办公时间访问&#xff0c;办公区的设备全天都可以访问3.1 测试&#xff1a…