【Java技术专题】「入门到精通系列教程」深入探索Java特性中并发编程体系的原理和实战开发指南( 实现可伸缩IO专题)— 上

news2025/1/13 15:58:27

深入探索Java特性中并发编程体系的原理和实战开发指南( 实现可伸缩IO专题)

  • 总体内容概览
  • 可扩展的网络服务
    • 分布式对象
    • 传统的阻塞式网络服务
      • 每个请求或连接可以在独立的线程中进行处理
        • Server服务处理请求类
        • Handler处理逻辑类
        • 优点
        • 缺点
    • 可扩展性目标
      • 平稳降级负载增加时
      • 持续改进随着资源增加
      • 满足可用性和性能目标
      • 分治法实现可伸缩性目标
    • 分治解决方案
      • 将处理划分为小任务
      • 事件驱动的执行
      • 使用Java NIO的非阻塞机制
      • 事件驱动设计的无穷可能性
  • 事件驱动
    • 特点分析
      • 资源损耗的减少
      • 更少的开销
      • 调度速度的优化
      • 类似于GUI事件驱动的操作
    • 问题分析
  • 反应堆模式
    • 单线程模式
      • 基本工作流程
      • 整体运行架构图(借鉴Reactor官网图)
      • 定义Reactor核心类
        • Reactor构造器
          • Acceptor类
        • run方法
        • dispatch方法
        • Handler对象类
          • Handler构造函数
          • run方法
            • read方法
            • send方法
      • 对于单线程模式优缺点分析
  • 参考资料

总体内容概览

通过使用Java NIO的非阻塞I/O API,可以实现高性能、高并发的网络服务。它提供了更灵活的I/O操作方式,能够处理大量的并发连接和请求,提供更好的可扩展性和响应能力。同时,结合事件驱动处理模型,可以实现高效的事件处理和资源利用。
在这里插入图片描述

  • 【可扩展的网络服务】指能够处理大量并发连接和请求的网络服务,它能够有效地利用系统资源,提供高性能和高吞吐量的服务,网络服务和分布式对象是现代计算中常见的概念,它们在构建分布式系统和提供网络功能方面起着重要的作用。
    在这里插入图片描述

  • 【事件驱动处理】是一种编程模型,其中程序的执行是由事件的发生和相应的事件处理器来驱动的。在可扩展的网络服务中,事件驱动处理模型常用于处理网络连接和请求。当有新的连接或请求到达时,会触发相应的事件,然后通过事件处理器来处理这些事件。

  • 【反应模式(Reactor Pattern)】是一种常见的事件驱动处理模式,它基于事件循环机制。在反应模式中,有一个事件循环(Event Loop)负责监听事件,并将事件分发给相应的事件处理器进行处理。这种模式可以提供高效的事件处理和响应能力。

  • 【Java NIO(New I/O)】是Java提供的非阻塞I/O API,它提供了一套用于处理I/O操作的新的API和机制。相比于传统的阻塞I/O,Java NIO提供了更高效的I/O操作方式,特别适用于构建可扩展的网络服务。它基于事件驱动的模型,通过选择器(Selector)和通道(Channel)来实现非阻塞的I/O操作。

可扩展的网络服务

网络服务是指在计算机网络上提供的各种服务和功能。它可以包括文件传输、网页生成、计算服务等。网络服务的基本结构通常包括读取请求、解码请求、处理服务、编码回复和发送回复等步骤。每个步骤的性质和成本可能因具体的服务类型而异。

分布式对象

分布式对象是指在分布式系统中的对象,它们可以在不同的计算节点上存在,并通过网络进行通信和交互。分布式对象的设计和实现需要考虑到分布式系统的特点,如节点间的通信、数据一致性、容错性等。分布式对象通常通过远程过程调用(RPC)或消息传递等方式进行通信和交互。

传统的阻塞式网络服务

在这里插入图片描述

每个请求或连接可以在独立的线程中进行处理

传统的阻塞式网络服务中,通常采用单线程或有限线程池来处理连接和请求。这种方式存在一些限制,因为所有的连接和请求都在同一个线程中顺序处理,一个请求的处理会阻塞其他请求的处理。

Server服务处理请求类
class Server implements Runnable {
 	public void run() {
	     try {
			 ServerSocket ss = new ServerSocket(PORT);
			 while (!Thread.interrupted())
			 new Thread(new Handler(ss.accept())).start();
		 // or, single-threaded, or a thread pool
		 } catch (IOException ex) { /* ... */ }
 }
Handler处理逻辑类
 static class Handler implements Runnable {
	 final Socket socket;
	 Handler(Socket s) { socket = s; }
	 public void run() {
		 try {
			 byte[] input = new byte[MAX_INPUT];
			 socket.getInputStream().read(input);
			 byte[] output = process(input);
			 socket.getOutputStream().write(output);
		 } catch (IOException ex) { /* ... */ }
		 }
	 	private byte[] process(byte[] cmd) { /* ... */ }
	 }
}
优点
  • 每个处理程序可以在它自己的线程中启动的情况下,每个连接或请求可以在独立的线程中并发处理。这样可以提高并发性和响应能力,充分利用多核处理器的性能。

  • 每个请求都可以在自己的线程中独立运行,不会受到其他请求的影响,互不干扰,这样可以更好地利用系统资源,提高响应速度和用户体验,还可以避免一个请求的处理阻塞其他请求的处理,提高系统的并发能力和吞吐量。

缺点
  • 在使用多线程处理网络服务时,需要考虑线程安全性和资源共享的问题。确保在多个线程之间正确管理和同步共享的数据和资源,避免出现竞态条件和数据不一致的问题。

  • 如果并发请求数量过大,每个请求都启动一个独立的线程,可能会导致系统资源的过度消耗和浪费。这可能会导致系统性能下降、响应时间延长,甚至引发资源耗尽的问题。

可扩展性目标

在这里插入图片描述

平稳降级负载增加时

在面对负载增加的情况下,为了保持系统的可用性和性能,可以采取平稳降级的策略。这意味着在负载增加时,系统可以根据优先级或其他规则,逐渐降低某些功能或服务的优先级,以确保核心功能的稳定性和性能。

持续改进随着资源增加

随着资源(如CPU、内存、磁盘、带宽)的增加,可以持续改进系统的性能和可用性。这可以包括优化算法和代码、增加缓存、提高数据库查询效率等措施,以更好地利用新增的资源,并提供更好的用户体验。

满足可用性和性能目标

在设计和开发网络服务时,需要明确可用性和性能目标,并确保系统能够满足这些目标。这包括提供短延迟、满足高峰需求、可调性的服务质量等。通过合理的架构设计、负载均衡、缓存和异步处理等技术手段,可以实现这些目标。

分治法实现可伸缩性目标

分治法是一种有效的方法,用于实现任何可伸缩性目标。通过将系统划分为多个独立的组件或服务,并使用适当的通信和协调机制,可以实现系统的可伸缩性。这样可以将负载分散到多个组件上,提高系统的并发处理能力和可扩展性。

分治解决方案

在这里插入图片描述

将处理划分为小任务

为了实现高性能和可伸缩性,可以将处理过程划分为小任务,每个任务执行一个不受阻塞的操作。这样可以避免长时间的阻塞操作,提高系统的并发能力和响应性。

事件驱动的执行

通过使用事件驱动的模型,可以在每个任务被启用时执行它。通常,一个IO事件作为触发器,当有IO事件发生时,相应的任务被调度和执行。这种方式可以提高系统的并发处理能力和资源利用率。

使用Java NIO的非阻塞机制

Java NIO提供了非阻塞的读写操作和感知IO事件的调度任务。通过使用Java NIO的非阻塞I/O API,可以实现高性能的网络通信,避免阻塞操作,提高系统的并发性和响应能力。

事件驱动设计的无穷可能性

事件驱动的设计模式和机制具有无穷无尽的变化可能性。通过合理设计和实现事件驱动的架构,可以满足不同的需求和场景,提供高性能、可扩展和灵活的网络服务。

事件驱动

特点分析

在这里插入图片描述

资源损耗的减少

采用事件驱动的模型可以减少资源的消耗。相比于为每个客户端分配一个线程,可以使用线程池或异步处理方式来管理并发连接和请求。这样可以减少线程的数量,降低资源消耗,并提高系统的并发能力。

更少的开销

事件驱动的模型可以减少上下文切换的次数,因为每个事件处理程序在自己的线程中独立执行。此外,可以使用非阻塞I/O操作和异步处理方式,减少锁定操作和阻塞等待,进一步降低开销。

调度速度的优化

为了提高调度速度,可以采用高效的事件循环机制和调度算法。可以使用优先级队列、定时器和事件分发机制等技术,优化事件的调度和处理过程,提高系统的响应速度和效率。

类似于GUI事件驱动的操作

事件驱动的模型在处理网络服务时类似于GUI(图形用户界面)的事件驱动操作。可以借鉴GUI开发中的事件处理机制和设计模式,将其应用于网络服务的事件驱动处理中,以提高代码的可维护性和可扩展性。
在这里插入图片描述

问题分析

  1. 编程困难:处理并发编程是一项具有挑战性的任务,需要考虑多个线程或进程之间的交互和同步。这可能导致编程变得复杂和困难。

  2. 无法消除所有阻塞:尽管反应堆模式可以提高系统的并发性和响应能力,但仍然无法消除所有的阻塞。某些因素,如垃圾回收和页面故障,可能会导致一些阻塞情况的发生。

  3. 必须跟踪服务的逻辑状态:在使用反应堆模式时,必须跟踪服务的逻辑状态。这是因为反应堆模式依赖于事件的发生和处理,需要确保正确地处理和分发事件,以保持服务的一致性和正确性。

反应堆模式

反应堆模式和AWT线程类似,都通过事件循环机制来调度和处理IO事件,并将其分发给适当的处理程序。这种模式可以提高系统的并发性和响应能力。

  • 管理绑定处理程序事件:可以通过管理绑定处理程序事件的方式来实现事件的分发和处理。

  • 处理程序执行非阻塞操作:网络处理程序可以执行非阻塞的操作。

单线程模式

在反应堆模式中,单线程模式是一种实现方式,它使用单个线程来处理所有的IO事件和请求。在这种模式下,所有的IO事件都被注册到一个事件循环中,并由单个线程按顺序处理。

基本工作流程

在这里插入图片描述

  • 注册IO事件:所有的IO事件都被注册到事件循环中,包括读取、写入、连接等操作。

  • 事件循环:单个线程按照注册的顺序循环遍历所有的IO事件,并根据事件的类型进行相应的处理。

  • 事件处理:对于每个IO事件,单线程模式会调用相应的处理程序来执行相应的操作,例如读取数据、写入数据或建立连接。

  • 非阻塞操作:在单线程模式下,处理程序执行非阻塞的操作,以避免阻塞其他事件的处理。

  • 事件分发:处理程序根据事件的类型将结果分发给相应的处理程序或回调函数。

整体运行架构图(借鉴Reactor官网图)

单线程的反应堆模式,通过使用Selector和ServerSocketChannel来处理IO事件和监听连接请求。它使用单线程来处理所有的事件,并通过附加对象的方式将具体的处理逻辑委托给相应的处理程序。
在这里插入图片描述
根据上述架构图,可以看出来,当客户端请求过来,直接请求到了就是Reactor对象,我们来定义和开发一下Reactor类以及方法逻辑。

定义Reactor核心类

Reactor类:这是主要的反应堆类,实现了Runnable接口,它包含一个Selector对象和一个ServerSocketChannel对象,用于处理IO事件和监听连接请求。

class Reactor implements Runnable {
	final Selector selector;
	final ServerSocketChannel serverSocket;
	Reactor(int port) throws IOException {
		selector = Selector.open();
		serverSocket = ServerSocketChannel.open();
		serverSocket.socket().bind(new InetSocketAddress(port));
		serverSocket.configureBlocking(false);
		SelectionKey sk = serverSocket.register(selector,SelectionKey.OP_ACCEPT);
		sk.attach(new Acceptor());
	}
	 // class Reactor continued
	public void run() { // normally in a newT hread
	 try {
		while (!Thread.interrupted()) {
			selector.select();
			Set selected = selector.selectedKeys();
			Iterator it = selected.iterator();
			while (it.hasNext())
				dispatch((SelectionKey)(it.next());
				selected.clear();
			}
		} catch (IOException ex) { /* ... */ }
	}
	void dispatch(SelectionKey k) {
		Runnable r = (Runnable)(k.attachment());
		if (r != null)
			r.run();
		}
	}
	
Reactor构造器

Reactor构造器中创建了一个Selector对象和一个ServerSocketChannel对象,并将ServerSocketChannel绑定到指定的端口。然后,将ServerSocketChannel注册到Selector上,以便监听连接请求。同时,将一个Acceptor对象附加到SelectionKey上,用于处理接受连接的事件。
在这里插入图片描述

Acceptor类

这是一个内部类,实现了Runnable接口。它用于处理接受连接的事件。在run方法中,通过调用serverSocket.accept()来接受连接,并创建一个新的Handler对象来处理该连接。

// class Reactor continued
	class Acceptor implements Runnable { // inner
		public void run() {
			try {
				SocketChannel c = serverSocket.accept();
				if (c != null)
					new Handler(selector, c);
				}
				catch(IOException ex) { /* ... */ }
				}
		}
}
run方法

通过调用selector.select()来等待IO事件的发生。一旦有事件发生,就会获取到被选中的SelectionKey集合,并遍历处理每个事件。在处理事件时,调用dispatch方法来执行相应的处理程序。

dispatch方法

dispatch方法根据SelectionKey的附加对象获取到对应的处理程序,并执行其run方法。这样可以将具体的处理逻辑委托给相应的处理程序。

在这里插入图片描述

Handler对象类

Handler是一个执行类,实现了Runnable接口。它包含一个SocketChannel对象和一个SelectionKey对象,用于与客户端进行通信。

final class Handler implements Runnable {
	final SocketChannel socket;
	final SelectionKey sk;
	ByteBuffer input = ByteBuffer.allocate(MAXIN);
	ByteBuffer output = ByteBuffer.allocate(MAXOUT);
	static final int READING = 0, SENDING = 1;
	int state = READING;
	Handler(Selector sel, SocketChannel c) throws IOException {
		socket = c; 
		c.configureBlocking(false);
		// Optionally try first read now
		sk = socket.register(sel, 0);
		sk.attach(this);
		sk.interestOps(SelectionKey.OP_READ);
		sel.wakeup();
	}
	
	void process() { /* ... */ }
	
	public void run() {
		try {
			if (state == READING) read();
			else if (state == SENDING) send();
		} catch (IOException ex) { /* ... */ }
	}
	
	void read() throws IOException {
		socket.read(input);
		if (inputIsComplete()) {
			process();
			state = SENDING;
			// Normally also do first write now
			sk.interestOps(SelectionKey.OP_WRITE);
		}
	}
	void send() throws IOException {
		socket.write(output);
		if (outputIsComplete()) 
			sk.cancel();
	}
}
Handler构造函数

将传入的SocketChannel对象配置为非阻塞模式,并将其注册到指定的Selector上。同时,将Handler对象附加到SelectionKey上,并设置对读取操作感兴趣。最后,调用Selector的wakeup方法,以确保Selector立即返回。

run方法

在run方法中,根据当前的状态(READING或SENDING)来执行相应的操作。如果处于READING状态,则调用read方法进行读取操作;如果处于SENDING状态,则调用send方法进行发送操作。

read方法

read方法用于从SocketChannel中读取数据。它将读取的数据存储到input ByteBuffer中,并检查是否已经完整读取了所需的数据。如果数据已经完整读取,则调用process方法进行处理,并将状态设置为SENDING。同时,将SelectionKey的兴趣操作设置为OP_WRITE,以便在下一次循环中进行写入操作。

send方法

send方法用于向SocketChannel中写入数据。它将output ByteBuffer中的数据写入到SocketChannel中,并检查是否已经完整发送了所有数据。如果数据已经完整发送,则取消SelectionKey的注册。

对于单线程模式优缺点分析

  • 优点:单线程模式的优点是简单和易于实现,因为只需要一个线程来处理所有的IO事件。

  • 缺点:无法充分利用多核处理器的性能,并且可能存在性能瓶颈。在高并发或高负载的情况下,可能需要考虑使用多线程或多进程的方式来处理IO事件。

对此我们进行改良,从而出现了多线程的反应堆模式,由于篇幅过长,在本篇文章就暂时不进行介绍多线程模式了,在下一篇文章【【Java技术专题】「入门到精通系列教程」深入探索Java特性中并发编程体系的原理和实战开发指南( 实现可伸缩IO专题)— 下】会对多线程模式以及线程池模式、终极模式(多个反应堆线程模式)进行深入分析和介绍说明。

参考资料

  • Doug Lea官方主页

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

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

相关文章

zotero word联动 如何使用Zotero在Word中插入参考文献

下载https://gitee.com/redleafnew00/Chinese-STD-GB-T-7714-related-csl里面论文要求的格式,或者word里面点这个再搜 输入好以后一定要输入空格!

RK3399平台开发系列讲解(基础篇)嵌入式编码规范有哪些

🚀返回专栏总目录 文章目录 一、什么是GNU二、GNU C 编码规范2.1、格式2.2、注释2.3、语法约定2.4、命名2.5、系统可移植性2.6、CPU 可移植性2.7、系统函数2.8、国际化2.9、字符集沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 GNU 编码规范的出发点,是确保所有 G…

【C语言】strcpy()函数

🦄个人主页:修修修也 🎏所属专栏:C语言 ⚙️操作环境:Visual Studio 2022 目录 一.strcpy()函数简介 1.函数功能 2.函数参数 1>.char * destination 2>.const char * source 3.函数返回值 4.函数头文件 二.strcpy()函数的具体使用 1.使用s…

【数据结构练习】树和二叉树的选择题精选集锦

前言 编程想要学的好,刷题少不了,我们不仅要多刷题,还要刷好题!为此我开启了一个弯道超车必做好题锦集的系列,此为树和二叉树的选择题精选集锦。该系列会不定期更新,敬请期待! 1.已知某二叉树的…

c++学习之搜索二叉树

目录 一,什么是搜索二叉树? 二,搜索二叉树的实现 非递归实现 节点与类成员 插入 查找 删除 递归实现 插入 查找 删除 一,什么是搜索二叉树? 搜索二叉树(Binary Search Tree)是一种常见…

jdbc 执行批处理任务

package com.csdn.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; //演示执行批处理任务 public class ExecuteBatchTask {public static void main(String[] args) throws ClassNotFou…

通过jsonobject.tostring 传字符串为有空格问题

目录 通过jsonobject.tostring 传字符串为有空格问题 1.问题原因解决思路解决方案总结参考 文章所属专区 项目问题解决 1.问题原因 通过JSONObject.toString()方法将字符串转换为JSON格式时,可能会出现空格的情况。这是因为JSONObject.toString()方法在生成JSON字…

理解什么是接口测试?怎样做接口测试?

一 什么是接口? 接口测试主要用于外部系统与系统之间以及内部各个子系统之间的交互点,定义特定的交互点,然后通过这些交互点来,通过一些特殊的规则也就是协议,来进行数据之间的交互。接口测试主要用于外部系统与系统之…

贪心算法学习——单调递增的数字

一&#xff0c;单调递增的数字 1.题目 当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。 给定一个整数 n &#xff0c;返回 小于或等于 n 的最大数字&#xff0c;且数字呈 单调递增 。 示例 1: 输入: n 10 输出: 9示例 2: 输入…

【c++速通】入门级攻略:引用详解 | auto的类型推导 | 不一样的for循环 | nullptr版本空指针

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; C入门到进阶 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言&#x1f324;️引用☁️引用的概念☁️引用的特性⭐引用在定义时必须初始化 ☁️常引用…

javascript IP地址正则表达式

注&#xff1a; 一定不要把表达式赋值给变量&#xff0c;直接表达式.test() /^(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2})\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2}|0)\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2}|0)\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2}|0)$/g.te…

【转载】双亲委派模型

双亲委派模型是 Java 类加载器的一种工作模式&#xff0c;通过这种工作模式&#xff0c;Java 虚拟机将类文件加载到内存中&#xff0c;这样就保证了 Java 程序能够正常的运行起来。那么双亲委派模型究竟说的是啥呢&#xff1f;接下来我们一起来看。 1.类加载器 双亲委派模型针…

驱动开发6 IO多路复用——epoll

核心操作&#xff1a;一棵树、一张表、三个接口 相关案例 #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys…

2022年12月 Python(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 关于Python语言的注释,以下选项中描述错误的是?( ) A: Python语言有两种注释方式:单行注释和多行注释 B: Python语言的单行注释以#开头 C: Python多行注释使用###来做为标记 D: …

RPA为什么会火起来?

RPA的前世今生 RPA&#xff08;Robotic Process Automation&#xff0c;机器人流程自动化&#xff09;&#xff0c;这项技术旨在模拟人类一系列应用软件中执行规则性的任务技术&#xff0c;通过精准、规范且搞笑的自动化处理过程&#xff0c;极大提高了日常业务开展的效率和准…

同一个页面同一区域两个el-table在v-if下样式重叠问题

&#x1f349;正常情况下在radio切换时两个表格的样式应如下 &#x1f349;实际上用v-if显示时会出现以下问题&#xff08;本该属于时间段相同模块的表格却出现在时间段自定义的表格中&#xff09; &#x1f349;解决方案&#xff1a; &#x1f343;一、将v-if替换成v-show(…

零基础Linux_23(多线程)线程安全+线程互斥(加锁)+死锁

目录 1. 线程安全 1.1 线程不安全前期 1.2 线程不安全原因 2. 线程互斥 2.1 加锁保护&#xff08;代码&#xff09; 2.2 锁的本质 3. 可重入对比线程安全 4. 死锁 4.1 死锁的必要条件 4.2 避免死锁 5. 笔试面试题 答案及解析 本篇完。 1. 线程安全 基于上一篇线程…

计算机网络(谢希仁)第八版课后题答案(第三章)

1.数据链路(即逻辑链路)与链路(即物理链路)有何区别? “电路接通了”与”数据链路接通了”的区别何在? 数据链路与链路的区别在于数据链路出链路外&#xff0c;还必须有一些必要的规程来控制数据的传输&#xff0c;因此&#xff0c;数据链路比链路多了实现通信规程所需要的硬…

​​​​​​​如何解决Google play开发者新注册账号,身份验证的地址证明问题?

我们知道&#xff0c;Google Play应用市场的发展速度惊人&#xff0c;但这两年&#xff0c;为了防止恶意软件的传播&#xff0c;谷歌要求开发者账号需要进行身份验证才能发布应用。 而今年越来越严格&#xff0c;不仅在提审时需要进行电话验证&#xff08;链接&#xff09;&am…