多线性开发实例分享

news2024/12/23 7:04:33

在这里插入图片描述

一. 概述

首先,在这里有必要和大家复现一下我使用该技术的背景:

在使用若依框架的时候,由于实际开发的需要,我需要配置四个数据源,并且通过mapper轮流去查每个库的指定用户数据,从而去判断改库是否存在目标数据,这样,就会产生一个问题,我需要查找四个库,由于网络限制等各方面因素,导致速度特别慢,这个时候我就在想,能不能使用线程并发,在请求一个库的同时,其余单个库的请求也同步进行,这样就会大大节约请求时间,提高效率,不会导致网络请求超时,所以就会出现一个问题,如何去使用多线程技术?

二. 混淆解析

多线程、多进程、异步、并发是计算机中常见的概念,它们都与程序的执行方式和效率相关。

  1. 多线程

多线程是指一个进程中同时运行多个线程,每个线程相对独立地执行不同的任务,共享进程的资源。Java语言提供了Thread类和Runnable接口以实现多线程编程。通过多线程可以提高CPU利用率,加快程序执行速度,但也会带来线程同步等问题。

举例:在Java中,创建新的线程通常使用Thread类或者Runnable接口,如下所示:

public class MyThread extends Thread{
  public void run(){
    // 执行某些操作
  }
}

public class MyRunnable implements Runnable{
  public void run(){
    // 执行某些操作
  }
}

// 创建并启动线程
MyThread thread = new MyThread();
thread.start();

MyRunnable runnable = new MyRunnable();
Thread thread2 = new Thread(runnable);
thread2.start();
  1. 多进程

多进程是指一个程序同时运行多个进程,每个进程有自己的独立地址空间,并与其他进程互相独立。多进程在保证数据隔离的同时,能够充分利用系统的资源,但是进程之间的通信也需要一定的开销和时间代价。

举例:在Java中,可以使用Runtime类和ProcessBuilder类创建新的进程。如下所示:

Runtime.getRuntime().exec("myCommand");

ProcessBuilder pb = new ProcessBuilder("myCommand");
Process process = pb.start();
  1. 异步

异步是指程序的执行方式,即某个操作不会阻塞代码的进程或线程,而是通过回调函数等方式在后台执行。异步通常使用事件驱动或者回调函数来实现。

举例:在Java中,可以使用Future和CompletableFuture类实现异步调用。如下所示:

Future<Integer> future = new CompletableFuture<>();
new Thread(() -> {
    // 执行一些耗时的操作
    int result = 123;
    // 将结果设置到future中
    future.complete(result);
}).start();

// 阻塞并等待结果
int result = future.get();
  1. 并发

并发是指多个任务同时执行,每个任务独立运行,并与其他任务相互交错执行。并发能够提高程序整体执行效率,但也会带来资源占用、线程同步等问题。

举例:在Java中,可以使用synchronized关键字和ReentrantLock类等机制来控制并发访问共享资源。如下所示:

public class Counter {
    private int count;

    public synchronized void increment() {
        count++; // 线程安全的自增操作
    }

    public int getCount() {
        return count; //非线程安全的获取操作
    }
}

总之,多线程、多进程、异步、并发等概念都与程序执行方式和效率相关,并且都需要仔细考虑各种问题,如线程安全、锁竞争等等。开发者需要根据具体业务需求选择合适的技术手段来实现程序。

三. 案例重现

        //辅助对象
        SysUser user = null;
        //培训信息用户
        RancoUser rancoUser = userService.selectUserByUserNameRanco(username);
        //服务信息用户
        SysUser server = userService.selectUserByUserNameServer(username);
        //客户信息用户
        SysUser customer = userService.selectUserByUserNameCustomer(username);
        //知识库用户
        SysUser knowledage = userService.selectUserByUserNameKnowledage(username);
        //判断那个不为空辅助对象为那个
        user = StringUtils.isNotNull(server) ? server : StringUtils.isNotNull(customer) ? customer : StringUtils.isNotNull(knowledage) ? knowledage : null;
        //服务,客户,知识三大模块的用户鉴权
        if (StringUtils.isNotNull(user)) {
            if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
                log.info("登录用户:{} 已被删除.", username);
                throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
            } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
                log.info("登录用户:{} 已被停用.", username);
                throw new ServiceException("对不起,您的账号:" + username + " 已停用");
            } else {
                passwordService.validate(user);
                return createLoginUser(user);
            }
        }
        //都为空进行输出
        else {
            log.info("登录用户:{} 不存在.", user.getUserName());
            throw new ServiceException("登录用户:" + user.getUserName() + " 不存在");
        }

在这里,我们可以清楚的看到

		//培训信息用户
       RancoUser rancoUser = userService.selectUserByUserNameRanco(username);
       //服务信息用户
       SysUser server = userService.selectUserByUserNameServer(username);
       //客户信息用户
       SysUser customer = userService.selectUserByUserNameCustomer(username);
       //知识库用户
       SysUser knowledage = userService.selectUserByUserNameKnowledage(username);

这四个每一次都会进行一次换库查询,注意,这里是换库,不是切换数据表

21:03:01.376 [pool-2-thread-2] INFO  c.r.f.d.DynamicDataSourceContextHolder - [setDataSourceType,26] - 切换到SLAVE3数据源
21:03:01.377 [pool-2-thread-3] INFO  c.r.f.d.DynamicDataSourceContextHolder - [setDataSourceType,26] - 切换到SLAVE2数据源
21:03:01.376 [pool-2-thread-1] INFO  c.r.f.d.DynamicDataSourceContextHolder - [setDataSourceType,26] - 切换到SLAVE1数据源

所以呢,是一个非常耗时的事情

四. 性能优化

对于代码进行分析,我们发现代码其实有很多的相似之处,所以我们对于代码进行如下优化

	    //辅助对象
        SysUser user = null;
        //服务信息用户
        SysUser server = null;
        //客户信息用户
        SysUser customer = null;
        //知识库用户
        SysUser knowledage = null;
        //创建一个包含3个线程的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        //将三个任务封装成Callable对象
        Callable serverCallable = new Callable() {
            public SysUser call() throws Exception {
                return userService.selectUserByUserNameServer(username);
            }
        };

        Callable customerCallable = new Callable() {
            public SysUser call() throws Exception {
                return userService.selectUserByUserNameCustomer(username);
            }
        };

        Callable knowledageCallable = new Callable() {
            public SysUser call() throws Exception {
                return userService.selectUserByUserNameKnowledage(username);
            }
        };

        //提交Callable任务给线程池并获取Future对象
        Future<SysUser> serverFuture = executorService.submit(serverCallable);
        Future<SysUser> customerFuture = executorService.submit(customerCallable);
        Future<SysUser> knowledageFuture = executorService.submit(knowledageCallable);

        //获取任务执行结果
        try {
            server = serverFuture.get();
            customer = customerFuture.get();
            knowledage = knowledageFuture.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }

        //关闭线程池
        executorService.shutdown();

        //判断那个不为空辅助对象为那个
        user = StringUtils.isNotNull(server) ? server : StringUtils.isNotNull(customer) ? customer : StringUtils.isNotNull(knowledage) ? knowledage : null;
        //服务,客户,知识三大模块的用户鉴权
        if (StringUtils.isNotNull(user)) {
            if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
                log.info("登录用户:{} 已被删除.", username);
                throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
            } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
                log.info("登录用户:{} 已被停用.", username);
                throw new ServiceException("对不起,您的账号:" + username + " 已停用");
            } else {
                passwordService.validate(user);
                return createLoginUser(user);
            }
        }
        //都为空进行输出
        else {
            log.info("登录用户:{} 不存在.", user.getUserName());
            throw new ServiceException("登录用户:" + user.getUserName() + " 不存在");
        }

代码解读

上述代码实现了一个多线程的登录校验功能,主要流程如下:

  1. 定义了四个辅助对象user、server、customer、knowledage,分别表示服务信息用户、客户信息用户、知识库用户。
  2. 创建一个包含3个线程的线程池executorService。
  3. 将三个任务封装成Callable对象,分别对应服务信息用户的查询、客户信息用户的查询和知识库用户的查询操作。
  4. 提交Callable任务给线程池并获取Future对象。
  5. 通过调用Future对象的get()方法获取任务执行结果,将结果赋值给对应的辅助对象user。
  6. 根据辅助对象user的不同值进行不同的处理,如果存在服务信息用户的辅助对象则进行服务鉴权,如果存在客户信息用户的辅助对象则进行客户鉴权,否则进行知识库鉴权。
  7. 如果存在有效的用户辅助对象,则进行密码校验并返回登录成功的用户;否则抛出异常提示用户不存在或已被停用。
  8. 最后输出登录失败的信息。

需要注意的是,在多线程环境下,对共享变量的操作需要考虑线程安全性。对于本例中的辅助对象user,可以使用ConcurrentHashMap来代替传统的Java集合类实现,以确保多个线程同时对其进行访问时的线程安全性。

五. 场景抉择

  1. 多线程:适用于需要同时处理多个任务并且这些任务之间存在一定的关联性,例如网络编程、UI编程等。在网络编程中,每个客户端连接可以对应一个线程,处理该客户端的请求,加速服务处理的效率;在UI编程中,需要有一个主线程负责事件响应和界面更新,而耗时的操作则需要放到其他线程中执行,以保证用户体验。

  2. 多进程:适用于需要不同进程之间进行资源隔离,并能充分利用系统资源的场景,例如操作系统管理、大规模数据处理等。在操作系统管理中,每个程序可以独立运行在自己的进程中,避免了进程间互相干扰和占用资源的问题;在大规模数据处理中,可以通过将大数据分成若干个小块,在不同进程中并行处理,提高数据处理的效率。

  3. 异步:适用于需要提高程序并发度,减少等待时间的场景,例如网络通信、IO操作等。在网络通信中,如果采用同步阻塞方式,一个请求的响应需要等待服务端完成业务才能返回,会阻塞客户端的主线程,影响其它任务的执行;而异步方式,程序可以在等待的同时继续执行后续任务,当服务端响应时再通过回调等方式进行处理,提高了程序的并发度和效率。

  4. 并发:适用于需要同时执行多个任务的场景,例如服务器编程、数据库访问等。在服务器编程中,可以通过支持多线程或者多进程来实现并发处理客户请求;在数据库访问中,多个客户端可以同时访问数据库,避免了因为单一的访问入口而导致的性能瓶颈等问题。

总之,不同的技术手段都有其对应的使用场景,开发者需要根据具体业务需求选择合适的技术方案。

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

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

相关文章

构建一个简易数据库-用C语言从头写一个sqlite的克隆 0.前言

英文源地址 一个数据库是如何工作的? 数据是以什么格式存储的(在内存以及在磁盘)?何时从内存中转移到此磁盘上?为什么每张表只能有一个主键?回滚一个事务是如何工作的?索引是以什么格式组织的?什么时候会发生全表扫描, 以及它是如何进行的?准备好的语句是以什么格式保…

#C2#S2.2~S2.3# 加入 factory/objection/virtual interface 机制

2.2 加入factory 机制 factory机制的实现被集成在了一个宏中&#xff1a;uvm_component_utils。这个宏所做的事情非常多&#xff0c;其中之一就是将my_driver登记在 UVM内部的一张表中&#xff0c;这张表是factory功能实现的基础。只要在定义一个新的类时使用这个宏&#xff0…

斐波那契数列相关简化4

看这篇文章前需要看下前面三篇文章&#xff0c;最起码第一第二篇是需要看一下的 斐波那契数列数列相关简化1_鱼跃鹰飞的博客-CSDN博客 斐波那契数列数列相关简化2_鱼跃鹰飞的博客-CSDN博客 算法玩的就是套路&#xff0c;练练就熟悉了 再来一个&#xff1a; 用1*2的瓷砖&am…

如何在 CentOS Linux 上安装和配置 DRBD?实现高可用性和数据冗余

DRBD&#xff08;Distributed Replicated Block Device&#xff09;是一种用于实现高可用性和数据冗余的开源技术。它允许在不同的服务器之间实时同步数据&#xff0c;以提供数据的冗余和容错能力。本文将详细介绍如何在 CentOS Linux 上安装和配置 DRBD。 1. 确认系统要求 在…

一文带你了解MySQL之InnoDB统计数据是如何收集的

前言 本文章收录在MySQL性能优化原理实战专栏&#xff0c;点击此处查看更多优质内容。 我们前边唠叨查询成本的时候经常用到一些统计数据&#xff0c;比如通过show table status可以看到关于表的统计数据&#xff0c;通过show index可以看到关于索引的统计数据&#xff0c;那…

MySQL之事务初步

0. 数据源 /*Navicat Premium Data TransferSource Server : localhost_3306Source Server Type : MySQLSource Server Version : 80016Source Host : localhost:3306Source Schema : tempdbTarget Server Type : MySQLTarget Server Version…

在线OJ常用输入规则

一、字符串输入规则 1.1 单行无空格字符串输入 输入连续字符串&#xff0c;cin默认空格/换行符为分割标志。 string s; //输入连续字符串&#xff0c;cin默认空格/换行符为分割标志。 cin >> s; 1.2 单行有空格字符串输入 getline函数接受带有空格的输入流&#xff…

C++——初识模板

文章目录 总述为什么要有模板函数模板概念函数模板使用方法函数模板的原理函数模板的实例化隐式示例化显式实例化 模板参数的匹配规则 类模板类模板的实例化 总述 本篇文章将带大家简单的了解一下c的模板方面的知识&#xff0c;带大家认识什么是模板&#xff0c;模板的作用&…

STL-常用算法(一.遍历 查找 排序)

目录 常用遍历算法&#xff1a; for_each和transform函数示例&#xff1a; 常用查找算法&#xff1a; find函数示例&#xff1a; find_if函数示例&#xff1a; adjacent_find示例&#xff1a; binary_search函数示例&#xff1a; count函数示例&#xff1a; count_if函…

训练/测试、过拟合问题

在机器学习中&#xff0c;我们创建模型来预测某些事件的结果&#xff0c;比如之前使用重量和发动机排量&#xff0c;预测了汽车的二氧化碳排放量 要衡量模型是否足够好&#xff0c;我们可以使用一种称为训练/测试的方法 训练/测试是一种测量模型准确性的方法 之所以称为训练…

springmvc升级到springboot2踩的坑

声明:删除springmvc的jar配置改成springboot的&#xff0c;若别的组件依赖springboot该升级就升级&#xff0c;该删掉就删掉&#xff0c;此文章只记录升级后的坑&#xff0c;升级springboot所需的jar请自行百度。 一.Hibernate的坑 概念:jpa和Hibernate的关系&#xff0c;jpa…

【JAVAEE】网络编程的简单介绍及其实现

目录 1.什么是网络编程 网络编程中的基本概念 常见的客户端服务端模型 2.Socket套接字 Socket套接字分类 举例对比TCP和UDP 3.UDP数据报套接字编程 DatagramSocket API DatagramPacket API InetSocketAddress API 4.实现一个简单的UDP回显服务器与客户端 服务端与客…

当前最新免费使用GPT-4方法汇总

目录 前言 温馨提示 Ora AI 使用方式 使用测试 Forefont chat 使用方式 使用测试 Perplexity AI 使用方式 使用测试 Poe 总结 前言 目前GPT-4的收费对于大多数人而言都还是不便宜&#xff0c;且付费方式复杂&#xff0c;使用上还有每3小时25个问题的限制&#xff…

Aspose.OCR For NET 23.5 Crack

使用几行代码将光学字符识别 (OCR) 添加到您的 .NET 应用程序。 适用于 .NET 的 Aspose.OCRAspose.OCR 文档 Aspose.OCR for .NET 是一个功能强大但易于使用且具有成本效益的光学字符识别 API。有了它&#xff0c;您可以用不到 5 行代码将 OCR 功能添加到您的 .NET 应用程序…

【Linux】初识优雅的Linux编辑器——Vim

❤️前言 大家好&#xff01;今天给大家带来的博客内容是关于Linux操作系统下的一款多模式文本编辑器Vim。本文将和大家一起来了解Vim编辑器的一些基础知识。 正文 Vim是一个多模式的文本编辑器(一共有十二种模式)&#xff0c;其中我们当我们初学Vim时主要了解如下三种工作模式…

Linux——多线程(线程概念|进程与线程|线程控制)

目录 地址空间和页表 如何看待地址空间和页表 虚拟地址如何转化到物理地址的 线程与进程的关系 什么叫进程&#xff1f; 什么叫线程&#xff1f; 如何看待我们之前学习进程时&#xff0c;对应的进程概念呢&#xff1f;和今天的冲突吗&#xff1f; windows线程与linux线…

Leetcode665. 非递减数列

Every day a Leetcode 题目来源&#xff1a;665. 非递减数列 解法1&#xff1a;贪心 本题是要维持一个非递减的数列&#xff0c;所以遇到递减的情况时&#xff08;nums[i] > nums[i 1]&#xff09;&#xff0c;要么将前面的元素缩小&#xff0c;要么将后面的元素放大。 …

K8s in Action 阅读笔记——【2】First steps with Docker and Kubernetes

K8s in Action 阅读笔记——【2】First steps with Docker and Kubernetes 2.1 Creating, running, and sharing a container image 2.1.1 Installing Docker and running a Hello World container 在电脑上安装好Docker环境后&#xff0c;执行如下命令&#xff0c; $ dock…

真会玩:莫言用ChatGPT为余华写了一篇获奖词

5月16日&#xff0c;《收获》杂志65周年庆典暨新书发布活动在上海舞蹈中心举行。 典礼现场&#xff0c;余华凭借《文城》获得收获文学榜2021年长篇小说榜榜首。 作为老友&#xff0c;莫言在颁奖时故意卖了个关子&#xff1a;“这次获奖的是一个了不起的人物&#xff0c;当然了&…

OMA通道-2

1 简介 本文档中指定的 API 使移动应用程序能够访问移动设备中的不同 SE&#xff0c;例如 SIM 或嵌入式 SE。 本规范提供了接口定义和 UML 图&#xff0c;以允许在各种移动平台和不同的编程语言中实现。 如果编程语言支持命名空间&#xff0c;则它应为 org.simalliance.openmob…