单机最快的队列Disruptor解析和使用

news2024/11/16 3:31:13

前言

介绍高性能队列Disruptor原理以及使用例子。

Disruptor是什么?

Disruptor是外汇和加密货币交易所运营商 LMAX group 建立高性能的金融交易所的结果。用于解决生产者、消费者及其数据存储的设计问题的高性能队列实现。可以对标JDK中的ArrayBlockingQueue。是目前单机且基于内存存储的最高性能的队列实现。见 与ArrayBlockingQueue性能对比。

Disruptor高性能秘诀

使用CAS代替锁

锁非常昂贵,因为它们在竞争时需要仲裁。这种仲裁是通过到操作系统内核的上下文切换来实现的,该内核将挂起等待锁的线程,直到它被释放。系统提供的原子操作CAS(Compare And Swap/Set)是很好的锁替代方案,Disruptor中同步就是使用的这种。

比如多生产者模式中com.lmax.disruptor.MultiProducerSequencer就是用了Java里sun.misc.Unsafe类基于CAS实现的API。

image-20210922160128392

等待策略com.lmax.disruptor.BlockingWaitStrategy使用了基于CAS实现的ReentrantLock。

image-20210922160301925

独占缓存行

为了提高效率CPU硬件不会以字节或字为单位移动内存,而是以缓存行,通常大小为 32-256 字节的缓存行,最常见的缓存行是 64 字节。这意味着,如果两个变量在同一个缓存行中,并且由不同的线程写入,那么它们会出现与单个变量相同的写入争用问题。为了获得高性能,如果要最小化争用,那么确保独立但同时写入的变量不共享相同的缓存行是很重要的。

比如com.lmax.disruptor.RingBuffer中属性前后都用未赋值的long来独占。com.lmax.disruptor.SingleProducerSequencerPad也有相同处理方式。

image-20210922151808613

image-20210922151833921

环形队列

  • 使用有界队列,减少线程争用

队列相比链表在访问速度上占据优势,而有界队列相比可动态扩容的无界队列则避免扩容产生的同步问题效率更高。Disruptor和JDK中的ArrayBlockingQueue一样使用有界队列。队列长度要设为2的n次幂,有利于二进制计算。

  • 使用环形数组,避免生产和消费速度差异导致队列头和尾争用

Disruptor在逻辑上将数组的的头尾看成是相连的,即一个环形数组(RingBuffer)。

  • Sequence

生产和消费都需要维护自增序列值(Sequence),从0开始。

生产方只维护一个代表生产的最后一个元素的序号。代表生产的最后一个元素的序号。每次向Disruptor发布一个元素都调用Sequenced.next()来获取下个位置的写入权。

在单生产者模式(SINGLE)由于不存在并发写入,则不需要解决同步问题。在多生产者模式(MULTI)就需要借助JDK中基于CAS(Compare And Swap/Set)实现的API来保证线程安全。

多个消费者各自维护自己的消费序列值(Sequence)保存数组中。

而环形通过与运算(sequence & indexMask)实现的,indexMask就是环形队列的长度-1。以环形队列长度8为例,第9个元素Sequence为8,8 & 7 = 0,刚好又回到了数组第1个位置。

见com.lmax.disruptor.RingBuffer.elementAt(long sequence)

image-20210922184325086

预分配内存

环形队列存放的是Event对象,而且是在Disruptor创建的时候调用EventFactory创建并一次将队列填满。Event保存生产者生产的数据,消费也是通过Event获取,后续生产则只需要替换掉Event中的属性值。这种方式避免了重复创建对象,降低JVM的GC产频率。

见com.lmax.disruptor.RingBuffer.fill(EventFactory eventFactory)

image-20210922184413900

消费者8种等待策略

当消费速度大于生产速度情况下,消费者执行的等待策略。

策略类名描述
BlockingWaitStrategy(常用)使用ReentrantLock,失败则进入等待队列等待唤醒重试。当吞吐量和低延迟不如CPU资源重要时使用。
YieldingWaitStrategy(常用)尝试100次,全失败后调用Thread.yield()让出CPU。该策略将使用100%的CPU,如果其他线程请求CPU资源,这种策略更容易让出CPU资源。
SleepingWaitStrategy(常用)尝试200次 。前100次直接重试,后100次每次失败后调用Thread.yield()让出CPU,全失败线程睡眠(默认100纳秒 )。
BusySpinWaitStrategy线程一直自旋等待,比较耗CPU。最好是将线程绑定到特定的CPU核心上使用。
LiteBlockingWaitStrategy与BlockingWaitStrategy类似,区别在增加了原子变量signalNeeded,如果两个线程同时分别访问waitFor()和signalAllWhenBlocking(),可以减少ReentrantLock加锁次数。
LiteTimeoutBlockingWaitStrategy与LiteBlockingWaitStrategy类似,区别在于设置了阻塞时间,超过时间后抛异常。
TimeoutBlockingWaitStrategy与BlockingWaitStrategy类似,区别在于设置了阻塞时间,超过时间后抛异常。
PhasedBackoffWaitStrategy根据时间参数和传入的等待策略来决定使用哪种等待策略。当吞吐量和低延迟不如CPU资源重要时,可以使用此策略。

消费者序列

所有消费者的消费序列(Sequence)都放在一个数组中,见com.lmax.disruptor.AbstractSequencer,通过SEQUENCE_UPDATER来更新对应的序列值。

image-20210922182346493

调用更新的地方在com.lmax.disruptor.RingBuffer.addGatingSequences(Sequence... gatingSequences)。

消费太慢队列满了怎么办?

生产者线程被阻塞。生产者调用Sequenced.next()争夺写入权的时候需要判断最小的消费序列值进行比较。如果写入的位置还未消费则会进入循环不断获取最小消费序列值进行比较。

见包com.lmax.disruptor下SingleProducerSequencer或MultiProducerSequencer中next(int n)方法。

image-20210922183913542

Disruptor开发步骤

  • 创建Event、EventFactory、EventHandler和ExceptionHandler类

Event是环形队列(RingBuffer)中的元素,是生产者数据的载体;EventFactory是定义Event创建方式的工厂类;EventHandler则是Event的处理器,定义如何消费Event中的数据。

另外有必要定义一个消费异常处理器ExceptionHandler,它是和EventHandler绑定的。当EventHandler.onEvent()执行抛出异常时会执行对应的异常回调方法。

  • 实例化Disruptor

创建Disruptor需要指定5个参数eventFactory、ringBufferSize、threadFactory、producerType、waitStrategy。

EventFactory是上面定义的Event工厂类;

ringBufferSize是环形队列的长度,这个值要是2的N次方;

threadFactory是定义消费者线程创建方式的工厂类;

producerType是指明生产者是一个(SINGLE)还是多个(MULTI)。默认是MULTI,会使用CAS(Compare And Swap/Set)保证线程安全。如果指定为SINGLE,则不使用没必要的CAS,使单线程处理更高效。

waitStrategy指明消费者等待生产时的策略。

  • 设置消费者

指明EventHandler并绑定ExceptionHandler。指定多个EventHandler时,会为每个EventHandler分配一个线程,一个Event会被多个并行EventHandler处理。

也可以指明多个WorkHandler,每个WorkHandler分配一个线程并行消费队列中的Event,一个Event只会被一个WorkHandler处理。

  • 创建/实例化EventTranslator

EventTranslator定义生产者数据转换为Event的方式,不同数量参数有不同的接口用来实现。

  • 最后用Disruptor.publishEvent() 来发布元素指明EventTranslator和参数

例子程序

  • 先引入Maven依赖
<dependency>
  <groupId>com.lmax</groupId>
  <artifactId>disruptor</artifactId>
  <version>3.4.4</version>
</dependency>
  • Event
/**
 * 事件
 *
 * @param <T>发布的数据类型
 */
public class MyEvent<T> {

    private T data;

    public T getData() {
        return data;
    }

    public MyEvent<T> setData(T data) {
        this.data = data;
        return this;
    }
}
  • EventFactory
import com.lmax.disruptor.EventFactory;

/**
 * 创建事件的工厂
 *
 * @param <T>发布的数据类型
 */
public class MyEventFactory<T> implements EventFactory<MyEvent<T>> {

    @Override
    public MyEvent<T> newInstance() {
        return new MyEvent<>();
    }
}
  • EventHandler
import com.lmax.disruptor.EventHandler;

/**
 * 事件消费方法
 *
 * @param <T>发布的数据类型
 */
public class MyEventHandler<T> implements EventHandler<MyEvent<T>> {

    @Override
    public void onEvent(MyEvent<T> tMyEvent, long l, boolean b) throws Exception {
        System.out.println(Thread.currentThread().getName() + "MyEventHandler消费:" + tMyEvent.getData());
    }
}
  • ExceptionHandler
import com.lmax.disruptor.ExceptionHandler;

/**
 * 消费者异常处理器
 *
 * @param <T>发布的数据类型
 */
public class MyExceptionHandler<T> implements ExceptionHandler<MyEvent<T>> {

    @Override
    public void handleEventException(Throwable ex, long sequence, MyEvent<T> event) {
        System.out.println("handleEventException");
    }

    @Override
    public void handleOnStartException(Throwable ex) {
        System.out.println("handleOnStartException");
    }

    @Override
    public void handleOnShutdownException(Throwable ex) {
        System.out.println("handleOnShutdownException");
    }
}

单消费者

import com.lmax.disruptor.EventTranslatorOneArg;
import com.lmax.disruptor.ExceptionHandler;
import com.lmax.disruptor.SleepingWaitStrategy;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import static com.lmax.disruptor.dsl.ProducerType.SINGLE;

/**
 * 单消费者
 */
public class SingleConsumerSample {

    public static void main(String[] args) {
        // 环形数组长度,必须是2的n次幂
        int ringBufferSize = 1024;
        // 创建事件(Event)对象的工厂
        MyEventFactory<String> eventFactory = new MyEventFactory<>();
        // 创建消费者线程工厂
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        // 等待策略
        WaitStrategy waitStrategy = new SleepingWaitStrategy();
        Disruptor<MyEvent<String>> disruptor =
                new Disruptor<>(eventFactory, ringBufferSize, threadFactory, SINGLE, waitStrategy);

        // 指定一个处理器
        MyEventHandler<String> eventHandler = new MyEventHandler<>();
        disruptor.handleEventsWith(eventHandler);
        // 处理器异常处理器
        ExceptionHandler<MyEvent<String>> exceptionHandler = new MyExceptionHandler<>();
        disruptor.setDefaultExceptionHandler(exceptionHandler);

        disruptor.start();

        // 通过事件转换器(EventTranslator)来指明如何将发布的数据转换到事件对象(Event)中
        // 这里是一个参数的转换器,另外还有两个(EventTranslatorTwoArg)、三个(EventTranslatorThreeArg)
        // 和多个(EventTranslatorVararg)参数的转换器可以使用,参数类型可以不一样
        EventTranslatorOneArg<MyEvent<String>, String> eventTranslatorOneArg =
                new EventTranslatorOneArg<MyEvent<String>, String>() {
                    @Override
                    public void translateTo(MyEvent<String> event, long sequence, String arg0) {
                        event.setData(arg0);
                    }
                };

        // 发布
        for (int i = 0; i < 10; i++) {
            disruptor.publishEvent(eventTranslatorOneArg, "One arg " + i);
        }

        disruptor.shutdown();
    }
}

单消费者Lambda写法

这种只是迎合Java8 Lambda语法特性,代码更简洁。

import com.lmax.disruptor.*;
import com.lmax.disruptor.dsl.Disruptor;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.stream.Collectors;

import static com.lmax.disruptor.dsl.ProducerType.SINGLE;

public class LambdaSample {


    public static void main(String[] args) {
        // 环形数组长度,必须是2的n次幂
        int ringBufferSize = 1024;
        // 创建消费者线程工厂
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        // 等待策略
        WaitStrategy waitStrategy = new SleepingWaitStrategy();
        Disruptor<MyEvent<String>> disruptor =
                new Disruptor<>(MyEvent::new, ringBufferSize, threadFactory, SINGLE, waitStrategy);

        // 指定一个处理器
        EventHandler<MyEvent<String>> eventHandler = (event, sequence, endOfBatch) ->
                System.out.println(Thread.currentThread().getName() + "MyEventHandler消费:" + event.getData());
        disruptor.handleEventsWith(eventHandler);
        // 处理器异常处理器
        ExceptionHandler<MyEvent<String>> exceptionHandler = new MyExceptionHandler<>();
        disruptor.setDefaultExceptionHandler(exceptionHandler);

        disruptor.start();

        // 通过事件转换器(EventTranslator)来指明如何将发布的数据转换到事件对象(Event)中
        // 一个参数的转换器
        disruptor.publishEvent((event, sequence, param) -> event.setData(param), "One arg ");
        // 两个参数的转换器
        disruptor.publishEvent((event, sequence, pA, pB) -> event.setData(pA + pB), "Two arg ", 1);
        // 三个参数的转换器
        disruptor.publishEvent((event, sequence, pA, pB, pC) -> event.setData(pA + pB + pC)
                , "Three arg ", 1, false);
        // 多个参数的转换器
        disruptor.getRingBuffer().publishEvent((event, sequence, params) -> {
            List<String> paramList = Arrays.stream(params).map(Object::toString).collect(Collectors.toList());
            event.setData("Var arg " + String.join(",", paramList));
        }, "param1", "param2", "param3");

        disruptor.shutdown();
    }
}

多消费者重复消费元素

关键只在于指定多个EventHandler,并且EventHandler还可以分别绑定不同的ExceptionHandler。

每个EventHandler分配一个线程,一个Event会被每个EventHandler处理,适合两个不同的业务都需要处理同一个元素的情况,类似广播模式。

import com.lmax.disruptor.*;
import com.lmax.disruptor.dsl.Disruptor;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import static com.lmax.disruptor.dsl.ProducerType.SINGLE;

/**
 * 一个元素多个消费者重复消费
 */
public class RepetitionConsumerSample {

    public static void main(String[] args) {
        // 环形数组长度,必须是2的n次幂
        int ringBufferSize = 1024;
        // 创建事件(Event)对象的工厂
        MyEventFactory<String> eventFactory = new MyEventFactory<>();
        // 创建消费者线程工厂
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        // 等待策略
        WaitStrategy waitStrategy = new SleepingWaitStrategy();
        Disruptor<MyEvent<String>> disruptor =
                new Disruptor<>(eventFactory, ringBufferSize, threadFactory, SINGLE, waitStrategy);


        // 这里指定了2个消费者,那就会产生2个消费线程,一个事件会被消费2次
        EventHandler<MyEvent<String>> eventHandler = (event, sequence, endOfBatch) ->
                System.out.println(Thread.currentThread().getName() + "MyEventHandler消费:" + event.getData());
        EventHandler<MyEvent<String>> eventHandler2 = (event, sequence, endOfBatch) ->
                System.out.println(Thread.currentThread().getName() + "MyEventHandler——2消费:" + event.getData());
        disruptor.handleEventsWith(eventHandler, eventHandler2);
        // 分别指定异常处理器
        ExceptionHandler<MyEvent<String>> exceptionHandler = new MyExceptionHandler<>();
        disruptor.handleExceptionsFor(eventHandler).with(exceptionHandler);
        disruptor.handleExceptionsFor(eventHandler2).with(exceptionHandler);

        disruptor.start();

        for (int i = 0; i < 10; i++) {
            disruptor.publishEvent((event, sequence, param) -> event.setData(param), "One arg " + i);
        }

        disruptor.shutdown();
    }
}

多消费者

关键只在于定义WorkHandler,然后实例化多个来消费。

每个WorkHandler分配一个线程,一个元素只会被一个WorkHandler处理。

import com.lmax.disruptor.ExceptionHandler;
import com.lmax.disruptor.SleepingWaitStrategy;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.WorkHandler;
import com.lmax.disruptor.dsl.Disruptor;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import static com.lmax.disruptor.dsl.ProducerType.SINGLE;

public class MultiConsumerSample {

    public static void main(String[] args) {
        // 环形数组长度,必须是2的n次幂
        int ringBufferSize = 1024;
        // 创建事件(Event)对象的工厂
        MyEventFactory<String> eventFactory = new MyEventFactory<>();
        // 创建消费者线程工厂
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        // 等待策略
        WaitStrategy waitStrategy = new SleepingWaitStrategy();
        Disruptor<MyEvent<String>> disruptor =
                new Disruptor<>(eventFactory, ringBufferSize, threadFactory, SINGLE, waitStrategy);

        // 处理器异常处理器
        ExceptionHandler<MyEvent<String>> exceptionHandler = new MyExceptionHandler<>();
        disruptor.setDefaultExceptionHandler(exceptionHandler);

        // 设置2个消费者,2个线程,一个Event只被一个消费者消费
        WorkHandler<MyEvent<String>> workHandler = tMyEvent ->
                System.out.println(Thread.currentThread().getName() + "WorkHandler消费:" + tMyEvent.getData());
        disruptor.handleEventsWithWorkerPool(workHandler, workHandler2);

        disruptor.start();

        for (int i = 0; i < 10; i++) {
            disruptor.publishEvent((event, sequence, param) -> event.setData(param), "One arg " + i);
        }

        disruptor.shutdown();
    }
}

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

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

相关文章

IDC报告背后:大模型时代,重新理解AI公有云

大模型之于AI公有云的意义&#xff0c;在于大模型可以改变过去“手工作坊定制算法”的高成本模式&#xff0c;转向“工厂模式”&#xff0c;只需要微调和精调&#xff0c;就可以形成针对性的场景算法。 作者|葛覃 出品|产业家 一年前&#xff0c;依然有不少云计算从业者思…

基于智能状态和源代码插桩的 C 程序内存安全性动态分析

原文来自微信公众号“编程语言Lab”&#xff1a;基于智能状态和源代码插桩的 C 程序内存安全性动态分析 搜索关注“编程语言Lab”公众号&#xff08;HW-PLLab&#xff09;获取更多技术内容&#xff01; 欢迎加入 编程语言社区 SIG-程序分析 参与交流讨论&#xff08;加入方式&a…

警惕!通过谷歌和必应搜索广告传播的新型恶意活动

据观察&#xff0c;一种新的恶意广告活动利用谷歌搜索和必应的广告&#xff0c;以AnyDesk、Cisco AnyConnect VPN和WinSCP等IT工具的用户为目标&#xff0c;诱骗他们下载木马安装程序&#xff0c;目的是入侵企业网络&#xff0c;并可能在未来实施勒索软件攻击。 Sophos在周三的…

Python生成pyc以及pyd文件的方法

文章目录 0. 背景1. pyc文件的生成2. pyd文件的生成3. 两者的异同 0. 背景 当有些模块的代码需要一定的保密性&#xff0c;这个时候就需要考虑pyc和pyd文件了。今天就好好琢磨一下这两种文件的生成和使用方法。让自己的知识能够朝着商业化的方向再前进一步。 1. pyc文件的生成…

为企业发展赋能增效:中国智能交通协会来访闪马智能

7月26日&#xff0c;中国智能交通协会秘书长杨颖一行来访闪马智能&#xff0c;闪马智能助理总裁兼营销与方案中心总经理黄智宏、CMO王一佳、副总裁詹诚以及副总裁兼智慧城市创新院院长邵钦豪等出席了交流会。 上海电科智能系统股份有限公司、卡斯柯信号有限公司、上海澳星照明电…

告别胆怯,大步向前,迎接新挑战!

告别胆怯&#xff0c;大步向前&#xff0c;迎接新挑战&#xff01; “赤日炎炎似火烧&#xff0c;野田禾稻半枯焦。农夫心内如汤煮。公子王孙把扇摇。”读罢《水浒传》中的这一首七绝诗&#xff0c;受其感染&#xff0c;笔者也乘兴呤顺口溜四句抒怀&#xff1a;“烈日炎炎似火…

FlatBuffers 使用编译器

1、前言 可能刚接触的人会思考为啥要使用编译器&#xff1a; 一般跨平台、跨语言的都有一套固定的流程&#xff0c;大致可分为&#xff1a; 撰写IDL文件 -> 使用对应语言的编译器&#xff0c;编译成对应的语言 -> 序列化 ->持久化 -> 反序列化 这里就对应着这个…

Spring中IOC容器常用的接口和具体的实现类

在Spring框架没有出现之前&#xff0c;在Java语言中&#xff0c;程序员们创建对象一般都是通过关键字new来完成&#xff0c;那时流行一句话“万物即可new&#xff0c;包括女朋友”。但是这种创建对象的方式维护成本很高&#xff0c;而且对于类之间的相互关联关系很不友好。鉴于…

三言两语说透关于 MySQL2 和 MySQL 的区别

MySQL是最流行的开源关系型数据库管理系统,拥有大量的使用者和广泛的应用场景。而MySQL2是MySQL官方团队推出的新一代MySQL驱动&#xff0c;用于取代老版的MySQL模块&#xff0c;提供更好的性能和更丰富的功能。本文将介绍MySQL2相较于MySQL有哪些优势以及具体的技术区别。 My…

01 关于 ABAP RAP 模型

ABAP RAP 模型 概览 关于 RAP 专栏内容&#xff0c;是个人在 SAP 官方提供的课程学习后整理的文档。这些文档涉及部分对概念的理解。在这里&#xff0c;不做具体讲解&#xff0c;而是跟随后续实例开发教程&#xff0c;通过实际练习&#xff0c;让大家能够理解这些概念。 ABAP …

【Linux】多线程——生产者和消费者模型

目录 1 生活中的例子 2 为何要使用生产者消费者模型 3 生产者和消费者模型的特点 优点 4 如何理解生产消费模型提高了效率&#xff1f; 5 基于BlockingQueue(阻塞队列)的生产者消费者模型 C queue模拟阻塞队列的生产消费模型 1 生活中的例子 存在多个消费者&#xff0c…

pve安装ikuai并设置,同时把pve的网络连接到ikuai虚拟机

目录 前因 前置条件 安装ikuai 进入ikuai的后台 配置lan口&#xff0c;以及wan口 配置lan口桥接 按实际情况来设置了 单拨&#xff08;PPOE拨号&#xff09; 多拨(内外网设置点击基于物理网卡的混合模式) 后续步骤 pve连接虚拟机ikuai的网络以及其他虚拟机连接ikuai的网…

QT中日期和时间类

QT中日期和时间类 QDateQTimeQDateTime QDate QDate类可以封装日期信息也可以通过这个类得到日期相关的信息, 包括:年, 月, 日。 // 构造函数 QDate::QDate(); QDate::QDate(int y, int m, int d);// 公共成员函数 // 重新设置日期对象中的日期 bool QDate::setDate(int year…

【数据结构与算法】整合一

GitHub同步更新&#xff08;已分类&#xff09;&#xff1a;Data_Structure_And_Algorithm-Review 公众号&#xff1a;URLeisure 的复习仓库 公众号二维码见文末 以下是本篇文章正文内容&#xff0c;下面案例可供参考。 吐血整理数据结构合集一&#xff1a; 整理了之前发的文…

SpringBoot集成Thymeleaf

Spring Boot 集成 Thymeleaf 模板引擎 1、Thymeleaf 介绍 Thymeleaf 是适用于 Web 和独立环境的现代服务器端 Java 模板引擎。 Thymeleaf 的主要目标是为开发工作流程带来优雅的自然模板&#xff0c;既可以在浏览器中正确显示的 HTML&#xff0c;也可以用作静态原型&#xf…

华为云NFS使用API删除大文件目录

最近在使用华为云SFS时&#xff0c;如果一个目录存储文件数超过100W&#xff0c;执行 “rm -rf path”时&#xff0c;存在删不动的情况&#xff0c;可以使用华为云API接口&#xff0c;执行异步删除。 华为官网&#xff1a; 删除文件系统目录_弹性文件服务 SFS_API参考_SFS Tu…

八方锦程15周年巡讲重磅启动,上海首场巡讲圆满举行

7月27日,八方锦程15周年巡讲——“与行业大咖同行 打造卓越人力资源官”活动在上海圆满举行! 作为八方锦程15周年巡讲的第一站,这一时刻,近200位与会人力资源从业者共同见证。 活动由八方锦程人才研究院副院长Leon主持并致开幕词。他向与会人员分享了举办该系列活动的初心,以八…

Mybatis源码-三种SqlSession的区别

三个SqlSession DefaultSqlSession与SqlSessionManager 与SqlSessionTemplate 是我常见的3种sqlsesion 从类图可以看出他们三个都实现了了SqlSession&#xff0c;也就是他们都可以表示一个会话。与其他不同的是SqlSessionManager实现了SqlSessionFactory 这三种sqlsession的区…

Windows 找不到文件‘chrome‘。请确定文件名是否正确后,再试一次

爱像时间&#xff0c;永恒不变而又短暂&#xff1b;爱像流水&#xff0c;浩瀚壮阔却又普普通通。 Windows 找不到文件chrome。请确定文件名是否正确后&#xff0c;再试一次 如果 Windows 提示找不到文件 "chrome"&#xff0c;可能是由于以下几种原因导致的&#xff1…

vmware中windows操作系统虚拟机安装

1.win10中安装 1.1 虚拟机向导 文件-新建虚拟机 典型-下一步 稍后安装操作系统-下一步 window10 64x -下一步 修改虚拟机名称及位置-下一步 默认60g,至少大于40g-将虚拟磁盘拆分成多个文件夹-下一步 点击完成 1.2 编辑虚拟机设置 移除打印机 设置虚拟机&#xff0c;加入iso映…