Reactor 网络模型、Java代码实例

2024-06-15 23:12

本文主要是介绍Reactor 网络模型、Java代码实例,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1. 概述
  • 2. Reactor 单线程模型
    • 2.1 ByteBufferUtil
    • 2.2 服务端代码
    • 2.3 客户端
    • 2.4 运行截图
  • 3. Reactor多线程模型
    • 3.1 服务端代码
    • 3.2 运行截图
  • 4. 主从 Reactor多线程模型
    • 4.1 服务端代码
    • 4.2 运行截图
  • 参考文献

1. 概述

在 I/O 多路复用的场景下,当有数据处于就绪状态后,需要一个事件分发器(Event Dispather),它负责将读写事件分发给对应的读写事件处理器(Event Handler)。

Reactor 模型主要分为三种

  • Reactor 单线程模型
  • Reactor 多线程模型
  • 主从 Reactor 多线程模型

Doug Lea 教授的课件 : https://gee.cs.oswego.edu/dl/cpjslides/nio.pdf

Java Socket 网络编程实例(阻塞IO、非阻塞IO、多路复用Selector、AIO)

2. Reactor 单线程模型

Reactor 单线程模型,是指所有I/O操作(监听服务端, 接受客户端连接请求;消息的读取、解码、编码、发送)都在同一个NIO线程上面完成
在这里插入图片描述

2.1 ByteBufferUtil

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;/*** ByteBufferUtil类提供了ByteBuffer和String之间转换的便捷方法。* 这些方法使用UTF-8编码进行转换,确保了数据的正确性和一致性。*/
public class ByteBufferUtil {/*** 从ByteBuffer中读取字符串。** @param byteBuffer 待读取的ByteBuffer,应确保其为读模式。* @return 从ByteBuffer解码得到的字符串。* @throws CharacterCodingException 如果解码过程中发生错误。*/public static String read(ByteBuffer byteBuffer) throws CharacterCodingException {// 使用UTF-8解码器将ByteBuffer中的字节解码为CharBuffer。CharBuffer charBuffer = StandardCharsets.UTF_8.decode(byteBuffer);// 将CharBuffer转换为字符串并返回。return charBuffer.toString();}/*** 将字符串写入ByteBuffer。** @param string 待写入的字符串。* @return 编码后的ByteBuffer。* @throws CharacterCodingException 如果编码过程中发生错误。*/public static ByteBuffer read(String string) throws CharacterCodingException {// 使用UTF-8编码器将字符串编码为ByteBuffer。return StandardCharsets.UTF_8.encode(string);}/*** 主函数用于演示ByteBuffer和字符串之间的相互转换。** @param args 命令行参数。* @throws CharacterCodingException 如果编码或解码过程中发生错误。*/public static void main(String[] args) throws CharacterCodingException {// 将字符串"test"编码为ByteBuffer,然后从ByteBuffer解码回字符串并打印。System.out.println(ByteBufferUtil.read(ByteBufferUtil.read("test")));}}

2.2 服务端代码

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.util.Set;public class Reactor implements Runnable {final Selector selector;final ServerSocketChannel serverSocket;/*** 初始化Reactor,打开选择器和服务器套接字通道,并注册接受操作。** @param port 服务器监听的端口号。* @throws IOException 如果打开选择器或服务器套接字通道失败。*/public 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());}/*** Reactor的主要运行方法,负责循环监听选择器上的事件,并分派处理。*/public void run() {try {while (!Thread.interrupted()) {selector.select();Set<SelectionKey> selected = selector.selectedKeys();System.out.println("selected:" + selected.size());for (SelectionKey selectionKey : selected) {dispatch(selectionKey);}selected.clear();}} catch (IOException ex) {ex.printStackTrace();}}/*** 根据选择键分派相应的处理逻辑。** @param k 选择键。* @throws IOException 如果发生I/O错误。*/void dispatch(SelectionKey k) throws IOException {Run r = (Run) (k.attachment());if (r != null)r.run();}/*** 接受者类,负责接受新的客户端连接。*/class Acceptor implements Run { // innerpublic void run() {try {SocketChannel c = serverSocket.accept();if (c != null)new Handler(selector, c);} catch (IOException ex) {ex.printStackTrace();}}}/*** 处理者类,负责处理客户端的读写操作。*/final class Handler implements Run {final SocketChannel socket;final SelectionKey sk;ByteBuffer input = ByteBuffer.allocate(1024);ByteBuffer output = ByteBuffer.allocate(1024);/*** 初始化处理者,注册读操作兴趣。** @param sel 选择器。* @param c   客户端套接字通道。* @throws IOException 如果注册操作失败。*/Handler(Selector sel, SocketChannel c)throws IOException {socket = c;c.configureBlocking(false);sk = socket.register(sel, 0);sk.attach(this);sk.interestOps(SelectionKey.OP_READ);sel.wakeup();}/*** 检查输入缓冲区是否已完成读取。** @return 如果输入缓冲区还有剩余数据,则返回true;否则返回false。*/boolean inputIsComplete() {return input.hasRemaining();}/*** 检查输出缓冲区是否已完成写入。** @return 如果输出缓冲区没有剩余空间,则返回true;否则返回false。*/boolean outputIsComplete() {return !output.hasRemaining();}/*** 处理输入缓冲区的数据。** @throws CharacterCodingException 如果字符编码转换失败。*/void process() throws CharacterCodingException {// 否则,将缓冲区反转并打印读取的数据input.flip();String request = ByteBufferUtil.read(input);System.out.println(request);input.clear();output = ByteBufferUtil.read("你好: " + request);}/*** 执行处理逻辑,包括读取数据、处理数据和准备写操作。** @throws IOException 如果发生I/O错误。*/public void run() throws IOException {socket.read(input);if (inputIsComplete()) {process();sk.attach(new Sender());sk.interestOps(SelectionKey.OP_WRITE);sk.selector().wakeup();}}/*** 发送者类,负责将处理后的数据写回客户端。*/class Sender implements Run {public void run() throws IOException {socket.write(output);if (outputIsComplete())  {new Handler(selector, socket);}}}}/*** 接口Run定义了所有处理逻辑的运行方法。*/public interface Run {public abstract void run() throws IOException;}/*** 程序入口点,创建并启动Reactor线程。** @param args 命令行参数。* @throws IOException 如果创建Reactor失败。*/public static void main(String[] args) throws IOException {Reactor reactor = new Reactor(6666);new Thread(reactor).start();while (true) ;}
}

2.3 客户端

import org.apache.commons.lang3.StringUtils;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;public class SelectorClient {public static void main(String[] args) throws IOException, InterruptedException {// 创建Socket通道并连接到服务器SocketChannel sc = SocketChannel.open();sc.connect(new InetSocketAddress("localhost", 6666));// 初始化输入和输出ByteBufferByteBuffer inputBuffer = ByteBuffer.allocate(512);ByteBuffer serverOutput = ByteBuffer.allocate(512);// 循环接收用户输入并发送给服务器while (true) {// 使用Scanner获取用户输入Scanner in = new Scanner(System.in);String input = in.nextLine();System.out.println("user input: " + input);if (StringUtils.isBlank(input)) {continue;}// 清空输入缓冲区,放入用户输入,然后反转准备写入inputBuffer.clear();inputBuffer.put(input.getBytes(StandardCharsets.UTF_8));inputBuffer.flip();// 将输入数据写入Socket通道sc.write(inputBuffer);System.out.println("send to server " + input);// 循环读取服务器响应int times = 1;while (true) {// 清空服务器响应缓冲区,准备读取数据serverOutput.clear();// 从Socket通道读取数据sc.read(serverOutput);// 如果没有读取到数据,继续尝试读取if (!serverOutput.hasRemaining()) {TimeUnit.SECONDS.sleep(1);times++;System.out.println(times);if (times > 10) {break;}continue;}// 反转缓冲区,读取数据并打印serverOutput.flip();System.out.println("server response " + ByteBufferUtil.read(serverOutput));// 读取完成后退出内层循环break;}}}
}

2.4 运行截图

在这里插入图片描述
在这里插入图片描述

3. Reactor多线程模型

Reactor多线程模型 和 Reactor单线程模型最大的区别就是有一组NIO线程来处理I/O操作:

  • 有一个NIO线程 Acceptor线程,监听服务端, 接受客户端连接请求
  • 网络I/O操作,读写(消息的读取、解码、编码、发送)等由一个NIO线程池负责
  • 一个NIO线程可以处理N条链路, 一个链路之对应一个NIO线程, 防止出现并发问题

在这里插入图片描述

3.1 服务端代码

服务端端代码如下,客户端同上:

import lombok.SneakyThrows;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;/*** 该类实现了使用线程池处理NIO服务器的逻辑。*/
public class ReactorWithThreadPool implements Runnable {/*** 处理器线程池,用于执行具体的处理任务。*/static ThreadPoolExecutor HANDLER_POOL = new ThreadPoolExecutor(2, 4,10, TimeUnit.MINUTES, new ArrayBlockingQueue<>(20), new ThreadPoolExecutor.CallerRunsPolicy());final Selector selector;final ServerSocketChannel serverSocket;/*** 创建一个NIO服务器,监听指定端口。** @param port 服务器监听的端口号。* @throws IOException 如果打开选择器或服务器套接字失败。*/public ReactorWithThreadPool(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());}/*** 主循环,负责监听选择器上的事件。*/public void run() {try {while (!Thread.interrupted()) {selector.select();Set<SelectionKey> selected = selector.selectedKeys();System.out.println("selected:" + selected.size());for (SelectionKey selectionKey : selected) {if (selectionKey.isReadable()) {System.out.println("selectionKey read");}if (selectionKey.isWritable()) {System.out.println("selectionKey write");}dispatch(selectionKey);}selected.clear();}} catch (IOException ex) {ex.printStackTrace();}}/*** 分派选择键对应的处理程序。** @param k 需要处理的选择键。* @throws IOException 如果操作通道失败。*/void dispatch(SelectionKey k) throws IOException {Run r = (Run) (k.attachment());if (r != null)r.run();}/*** 接受者处理程序,负责接受新的客户端连接。*/class Acceptor implements Run { // innerpublic void run() {try {SocketChannel c = serverSocket.accept();if (c != null)new Handler(selector, c);} catch (IOException ex) {ex.printStackTrace();}}}/*** 处理客户端请求的处理程序。*/final class Handler implements Run {final SocketChannel socket;final SelectionKey sk;ByteBuffer input = ByteBuffer.allocate(1024);ByteBuffer output = ByteBuffer.allocate(1024);/*** 创建一个新的处理程序实例。** @param sel 选择器。* @param c   客户端套接字通道。* @throws IOException 如果注册选择键或配置套接字失败。*/Handler(Selector sel, SocketChannel c)throws IOException {socket = c;c.configureBlocking(false);sk = socket.register(sel, 0);sk.attach(this);sk.interestOps(SelectionKey.OP_READ);sel.wakeup();}/*** 检查输入缓冲区是否已完成读取。** @return 如果输入缓冲区还有剩余,则为true;否则为false。*/boolean inputIsComplete() {return input.hasRemaining();}/*** 检查输出缓冲区是否已完成发送。** @return 如果输出缓冲区没有剩余,则为true;否则为false。*/boolean outputIsComplete() {return !output.hasRemaining();}/*** 处理输入数据。** @throws CharacterCodingException 如果字符编码失败。*/void process() throws CharacterCodingException {// 否则,将缓冲区反转并打印读取的数据input.flip();String request = ByteBufferUtil.read(input);System.out.println(request);input.clear();output = ByteBufferUtil.read("你好: " + request);}/*** 读取客户端输入,并根据情况启动处理程序或发送器。** @throws IOException 如果读取通道失败。*/public void run() throws IOException {socket.read(input);if (inputIsComplete()) {HANDLER_POOL.execute(new Processor());}}/*** 发送器处理程序,负责向客户端发送数据。*/class Sender implements Run {public void run() throws IOException {socket.write(output);if (outputIsComplete()) {new Handler(selector, socket);}}}/*** 处理请求的处理程序,负责处理输入数据并准备输出。*/class Processor implements Runnable {@Override@SneakyThrowspublic void run() {process();sk.attach(new Sender());sk.interestOps(SelectionKey.OP_WRITE);sk.selector().wakeup();}}}/*** 处理器接口,定义了处理程序应实现的运行方法。*/public interface Run {public abstract void run() throws IOException;}/*** 程序入口点。** @param args 命令行参数。* @throws IOException 如果启动服务器失败。*/public static void main(String[] args) throws IOException {ReactorWithThreadPool reactor = new ReactorWithThreadPool(6666);new Thread(reactor).start();while (true) ;}
}

3.2 运行截图

在这里插入图片描述
在这里插入图片描述

4. 主从 Reactor多线程模型

主从 Reactor多线程模型的特点:服务端接受客户端连接,不再是一个单独的NIO线程,而是一个独立的NIO线程池。

Acceptor 接收到客户端TCP连接请求并处理完成后, 将新创建的SocketChannel 注册到 I/O线程池 (sub Reactor)。

Acceptor线程池仅负责客户端的登陆、握手、安全认证, 一旦链路建立成功, 就将链路注册到 I/O线程池 (sub Reactor), I/O线程池 (sub Reactor)负责后续的 I/O操作
在这里插入图片描述

4.1 服务端代码

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.SneakyThrows;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class MultiReactor implements Runnable {Selector selector = null;ServerSocketChannel serverSocket;static ThreadPoolExecutor REACTOR_THREAD_POOL = new ThreadPoolExecutor(2, 16,10, TimeUnit.MINUTES, new ArrayBlockingQueue<>(20),new ThreadFactoryBuilder().setNameFormat("REACTOR_THREAD_POOL-%d").build(), new ThreadPoolExecutor.CallerRunsPolicy());/*** 构造函数,初始化多线程反应器。** @param port 服务器监听端口。* @throws IOException 如果打开selector或服务器SocketChannel时发生错误。*/public MultiReactor(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(serverSocket));}/*** 构造函数,使用已有的selector。** @param selector 已打开的selector。*/public MultiReactor(Selector selector) throws IOException {this.selector = selector;}/*** 主运行方法,负责监听和分发事件。*/@Overridepublic void run() {try {while (!Thread.interrupted()) {System.out.println(Thread.currentThread() + " select start");selector.select();Set<SelectionKey> selected = selector.selectedKeys();System.out.println(Thread.currentThread() + " " + "selected:" + selected.size());for (SelectionKey selectionKey : selected) {if (selectionKey.isReadable()) {System.out.println("selectionKey read");}if (selectionKey.isWritable()) {System.out.println("selectionKey write");}dispatch(selectionKey);}selected.clear();}} catch (IOException ex) {ex.printStackTrace();}}/*** 分发已选择的事件到相应的处理程序。** @param k 选择的关键。* @throws IOException 如果操作通道时发生错误。*/void dispatch(SelectionKey k) throws IOException {Run r = (Run) (k.attachment());if (r != null)r.run();}/*** Acceptor类负责接受新的客户端连接,并将它们分配给子反应器处理。*/class Acceptor implements Run {private final ServerSocketChannel listenSocketChannel;private final List<MultiReactor> subReactors = new ArrayList<>(ACCEPTOR_POOL_NUM);private static final int ACCEPTOR_POOL_NUM = 4;private final ThreadPoolExecutor ACCEPTOR_POOL = new ThreadPoolExecutor(ACCEPTOR_POOL_NUM, ACCEPTOR_POOL_NUM,10, TimeUnit.MINUTES, new ArrayBlockingQueue<>(20),new ThreadFactoryBuilder().setNameFormat("ACCEPTOR_POOL-%d").build(), new ThreadPoolExecutor.CallerRunsPolicy());Acceptor(ServerSocketChannel listenSocketChannel) throws IOException {this.listenSocketChannel = listenSocketChannel;for (int i = 0; i < ACCEPTOR_POOL_NUM; i++) {MultiReactor subReactor = new MultiReactor(Selector.open());subReactors.add(subReactor);ACCEPTOR_POOL.execute(subReactor);}}/*** 接受新的客户端连接,并分配给子反应器处理。*/@Overridepublic void run() {try {SocketChannel clientSocketChannel = listenSocketChannel.accept();// 设置为非阻塞// 任意选择一个从Reactor,让其监听连接的客户端的READ事件Optional<MultiReactor> anySubReactor = subReactors.stream().findAny();if (anySubReactor.isPresent() && clientSocketChannel != null) {MultiReactor subReactor = anySubReactor.get();System.out.println(Thread.currentThread() + ": "+ subReactor);new Handler(subReactor.selector, clientSocketChannel);}} catch (IOException e) {e.printStackTrace();}}}/*** Handler类负责处理与客户端的通信,包括读取请求和发送响应。*/final class Handler implements Run {final SocketChannel socket;final SelectionKey sk;ByteBuffer input = ByteBuffer.allocate(1024);ByteBuffer output = ByteBuffer.allocate(1024);Handler(Selector sel, SocketChannel c)throws IOException {socket = c;c.configureBlocking(false);sel.wakeup();sk = socket.register(sel, SelectionKey.OP_READ);sk.attach(this);sk.interestOps(SelectionKey.OP_READ);}/*** 检查输入缓冲区是否已完成读取。** @return 如果输入缓冲区还有剩余,则返回true;否则返回false。*/boolean inputIsComplete() {return input.hasRemaining();}/*** 检查输出缓冲区是否已完成发送。** @return 如果输出缓冲区没有剩余,则返回true;否则返回false。*/boolean outputIsComplete() {return !output.hasRemaining();}/*** 处理输入数据,将其解码并准备生成响应。** @throws CharacterCodingException 如果字符编码发生错误。*/void process() throws CharacterCodingException {// 否则,将缓冲区反转并打印读取的数据input.flip();String request = ByteBufferUtil.read(input);System.out.println(Thread.currentThread() + ": " + request);input.clear();System.out.println(input.toString());System.out.println(output.toString());output = ByteBufferUtil.read("你好: " + request);}/*** 读取客户端请求,并根据需要启动处理过程。** @throws IOException 如果读取通道时发生错误。*/@Overridepublic void run() throws IOException {socket.read(input);if (inputIsComplete()) {REACTOR_THREAD_POOL.execute(new Processor(sk.selector()));}}/*** Sender类负责发送响应给客户端。*/class Sender implements Run {private Selector selector;public Sender(Selector selector) {this.selector = selector;}/*** 发送输出缓冲区中的数据到客户端。** @throws IOException 如果写入通道时发生错误。*/public void run() throws IOException {System.out.println("start write");socket.write(output);if (outputIsComplete()) {new Handler(this.selector, socket);}}}/*** Processor类负责处理请求并准备响应。*/class Processor implements Runnable {private Selector selector;public Processor(Selector selector) {this.selector = selector;}@Override@SneakyThrowspublic void run() {process();sk.attach(new Sender(this.selector));sk.interestOps(SelectionKey.OP_WRITE);sk.selector().wakeup();}}}/*** Run接口定义了处理事件的运行时行为。*/public interface Run {void run() throws IOException;}/*** 程序入口点。** @param args 命令行参数。* @throws IOException 如果初始化反应器时发生错误。*/public static void main(String[] args) throws IOException {MultiReactor reactor = new MultiReactor(6666);new Thread(reactor).start();while (true) ;}
}

4.2 运行截图

在这里插入图片描述
在这里插入图片描述

参考文献

  • Doug Lea 教授的课件 : https://gee.cs.oswego.edu/dl/cpjslides/nio.pdf
  • Netty权威指南(第2版)李林锋 / 著
  • https://juejin.cn/post/7210375522512666679?searchId=20240612213218FE474007F2FADD0130AA

在这里插入图片描述

这篇关于Reactor 网络模型、Java代码实例的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1064847

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G