java 线程之间通信-volatile 和 synchronized

你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益:

  1. 了解大厂经验
  2. 拥有和大厂相匹配的技术等

希望看什么,评论或者私信告诉我!

文章目录

  • 一、背景
  • 二、线程之间通信的基本储备
    • 2.1 为什么线程之间需要通信
    • 2.2 现代处理器结构
    • 2.3 基于现代处理器结构下的线程运行模型
    • 2.4 线程工作内存带来的问题
  • 三、volatile 和 synchronized
    • 3.1 volatile是什么
      • 3.1.1 可见性
      • 3.1.2 禁止指令重排序优化
      • 3.1.3 内存屏障(Memory Barrier)
      • 3.1.4 不保证原子性
      • 3.1.5 使用场景
    • 3.2 volatile 原理和例子
    • 3.2 synchronized 是什么
      • 3.2.1. 基本用法
        • 3.2.1.1 同步方法
        • 3.2.1.2 同步代码块
      • 3.2.2. 工作原理
      • 3.2.3. 保证原子性和可见性
    • 3.3 synchronized 原理和例子
  • 四、总结


一、背景

前面两篇,我们分别知道了 关于 java 多线程,你需要知道的一些基础知识 以及 关于 java 多线程,你需要知道的一些基础知识
本篇文章呢,我们继续死磕 java 多线程,来聊一聊 java 多线程之间是如何通信的-volatile 和 synchronized

二、线程之间通信的基本储备

2.1 为什么线程之间需要通信

在线程编程中,线程之间需要相互通信的主要原因是协作和数据共享。以下是一些主要的原因:

  1. 资源共享: 多个线程可能需要同时访问和操作共享的资源,如数据结构、文件、网络连接等。为了避免数据竞争和确保数据的一致性,线程之间需要通信来协调对共享资源的访问。

  2. 任务分工: 在多线程编程中,不同线程可以负责不同的任务或子任务,线程之间需要协作和通信以完成整体的工作。通信可以是单向的传达结果,也可以是双向的交换信息和状态。

  3. 同步操作: 线程之间的通信可以用于同步操作,例如一个线程需要等待另一个线程完成某个任务后才能继续执行,或者通知其他线程某个事件已发生。

  4. 数据传递: 线程之间通过通信来传递数据和消息,以便于协调工作、共享信息或通知事件。

  5. 提高性能: 在某些情况下,通过线程之间的通信和协作可以提高程序的性能,例如使用线程池执行并发任务时,可以减少线程的创建和销毁开销。

  6. 实现某些模型和算法: 有些并发模型和算法需要线程之间的协作和通信,如生产者消费者模型、并发队列等。

综上所述,线程之间的通信在多线程编程中是非常重要的,能够实现不同线程之间的协作、数据共享、任务分工等功能,从而有效利用计算资源并实现复杂的并发任务。

2.2 现代处理器结构

一般情况下,现代处理器通常会包含一级缓存(L1 Cache)、二级缓存(L2 Cache)、和三级缓存(L3 Cache)。这些缓存一般都是集成在处理器芯片内部的,被称为“内置缓存”(On-Chip Cache),用于加快数据的访问速度和减少对主内存的访问。这里有一个 2020年的数据

L1 cache 访问时间是 1ns
L2 cache 访问时间是 4ns
主存访问时间是 100ns

具体关于三级缓存(L3 Cache)通常的情况包括:

  1. 位置: L3 Cache通常位于处理器芯片内部,但是相对于L1和L2 Cache,它的容量更大,从几MB到几十MB不等。
  2. 作用: L3 Cache主要用来缓存多个核心之间共享的数据,以提高处理器核心之间的数据共享效率。
  3. 互相之间是否独立: 不同处理器核心之间可以访问共享的L3 Cache,这有助于提高多个核心之间数据访问的效率。

由于L3 Cache容量较大且能够为多个核心提供共享数据缓存,因此它可以有效地提高多核处理器系统中核心之间的数据共享和协作效率。虽然L3 Cache通常也集成在处理器芯片内部,但某些处理器架构可能会将部分L3 Cache放置在处理器芯片外部以适应不同的需求。
在这里插入图片描述
当系统运行时,CPU执行计算的过程如下:

  1. 程序以及数据被加载到主内存
  2. 指令和数据被加载到CPU缓存
  3. CPU执行指令,把结果写到高速缓存
  4. 高速缓存中的数据写回主内存

2.3 基于现代处理器结构下的线程运行模型

在这里插入图片描述

线程的工作内存其实就是多级缓存以及CPU寄存器的抽象。

所以每个线程在运行时确实都会有自己的一套完整的上下文,这个上下文包含了线程当前的状态、执行环境以及必要的数据。这个上下文在线程切换时被保存和恢复,以确保线程可以从中断点继续执行。下面是上下文的一些重要组成部分:

  1. 寄存器状态: 在上下文中,包含了线程当前寄存器的状态。寄存器存储了当前线程的执行环境,包括程序计数器、栈指针、各种通用寄存器等。

  2. 栈: 程序使用的栈包含了函数调用、局部变量以及其他执行环境的信息。每个线程都有自己的栈,用于存储执行过程中的临时数据。

  3. 程序计数器(Program Counter): 程序计数器存储着当前执行的指令地址或者是下一条待执行的指令地址。

  4. 线程状态: 这部分描述了线程当前所处的状态,比如运行态、就绪态、阻塞态等。线程状态的改变会触发线程调度。

  5. 权限和访问控制: 上下文中可能包含了线程的权限信息,以确保线程的操作在合适的权限下进行。

  6. 堆栈指针信息: 记录了当前线程堆栈的指针位置,在进行栈操作时起着重要作用。

  7. 资源分配信息: 上下文还可能包括了线程已获得的资源、线程使用的栈大小等信息。

通过保存和恢复这些上下文信息,操作系统能够有效地管理多个线程的执行,实现线程之间的切换和并发执行。这些上下文信息是确保线程能够在不同执行环境下正确运行的关键。

2.4 线程工作内存带来的问题

线程有自己的工作内存最大的问题之一就是数据不一致性问题。这种情况可以导致程序出现难以预测的错误行为,因为多个线程可能会在各自的工作内存中缓存共享变量的副本,而这些副本的更新可能无法即时反映到其他线程中,引发数据的不一致性。

数据不一致性问题的一些常见情形包括:

  1. 写问题(Write Problem): 一个线程在自己的工作内存中修改了共享变量但未立即写回主内存,导致其他线程无法立即看到这个变化。

  2. 读问题(Read Problem): 一个线程从主内存中读取了共享变量的值,但由于另一个线程已经对该变量进行了修改,该线程的工作内存中的值已经过时,导致读取的值不正确。

  3. 指令重排序问题: 编译器或处理器可能会对指令进行重排序优化,这可能导致线程在不同的顺序下访问共享变量,进而使得数据出现不一致的情况。

其实就是两类问题:

  1. 数据可见性的问题,即另外一个线程改了共享变量,我能不能马上知道。
  2. 共享变量一致性的问题,即多个线程操作同一个共享变量,它的结果能不能跟非多线程操作同一个共享变量的结果一致。

为了解决上述的两个问题,我们引入了 volatile 和 synchronized

三、volatile 和 synchronized

3.1 volatile是什么

volatile 是 Java 中的一个关键字,主要用于确保多线程环境下变量的可见性和有序性。当一个变量被声明为 volatile 时,意味着这个变量可能会被多个线程同时访问和修改,因此需要使用 volatile 来确保所有线程都能看到变量的最新值。

下面是 volatile 的详细解释:

3.1.1 可见性

在多线程环境中,一个线程修改了一个共享变量的值,另一个线程无法立即看到这个变化,除非它主动读取该变量的值。而 volatile 关键字保证了当一个线程修改了一个 volatile 变量的值后,其他线程会立即看到这个变化。这是因为 volatile 变量不会被缓存到线程的工作内存中,每次读取 volatile 变量时都会直接从主内存中获取最新值。

3.1.2 禁止指令重排序优化

编译器和处理器可能会对代码进行指令重排序优化,以提高执行效率。但是,在某些情况下,这种重排序可能会导致多线程环境下的数据不一致问题。volatile 关键字可以防止这种情况发生,它会告诉编译器和处理器不要对这个变量进行重排序优化。

3.1.3 内存屏障(Memory Barrier)

现代处理器为了提高性能会对内存操作进行优化,包括重新排序读写操作。为了强制处理器的操作符合程序员的预期顺序,编译器会在使用volatile的读/写操作周围插入内存屏障(Memory Barrier)。这些内存屏障是一种同步机制,用于阻止屏障两侧的指令被重排序。通过确保特定的读写操作顺序执行,内存屏障确保了程序中的某些操作在特定时刻已经执行完成或已经启动执行,从而在多线程环境中维持操作的有序性。

3.1.4 不保证原子性

虽然 volatile 可以保证可见性和禁止指令重排序优化,但它不能保证复合操作(如自增或自减等)的原子性。如果需要一个变量在多线程环境下的原子性操作,如计数或状态更新等,应该使用 AtomicIntegerAtomicLong 等原子类或者使用 synchronized 块来保证原子性。但对于简单的状态标记(例如一个标志位),volatile 是足够的。

3.1.5 使用场景

volatile 主要用于以下场景:

  1. 状态标记:例如用于标记某个任务是否已经完成。在这种情况下,一个线程会检查这个标记,然后基于这个标记的值来决定下一步行动。如果这个标记在多线程环境下被使用,并且可能被多个线程同时修改,那么就需要使用 volatile 来确保每个线程都能看到最新的标记值。
  2. 单例模式的双重检查锁定:在某些单例模式的实现中,会使用 volatile 来确保实例在多个线程间的正确创建和访问。

总的来说,volatile 是一个轻量级的同步机制,用于确保多线程环境下变量的可见性和有序性。但在需要复杂同步操作或原子性保证的情况下,可能需要考虑使用更强大的同步机制,如 synchronized 块或锁。

3.2 volatile 原理和例子

volatile 状态标记 的例子:

public class StopThreadExample {

    // 使用volatile修饰的变量来确保所有线程都能看到最新的标志位值
    private volatile boolean flag= true;

    // 一个任务执行线程,它持续检查标志位并根据标志位决定是否继续运行
    public void runTask() {
        while (isRunning) { // 使用volatile修饰的变量确保我们能正确读取到最新的状态
            // 这里模拟一些耗时任务
            System.out.println("执行任务...");
            try {
                Thread.sleep(1000); // 模拟耗时操作,这里暂停一秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("任务执行线程已经停止.");
    }

    public static void main(String[] args) throws InterruptedException {
        StopThreadExample example = new StopThreadExample();
        Thread taskThread = new Thread(() -> example.runTask()); // 启动任务执行线程
        taskThread.start(); // 开始执行任务线程
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ; // 让主线程暂停几秒以便观察效果,模拟主线程正在做其他事情的情况
        System.out.println("停止任务执行线程..."); // 在主线程中通知任务执行线程停止执行
        example.flag= false; // 设置标志位来停止任务执行线程,所有线程都会看到最新的标志位值,因为使用了volatile关键字修饰isRunning变量
        taskThread.join(); // 确保任务执行线程停止后主线程继续执行接下来的代码或结束程序(这一步依赖于主线程的下一步动作)
        System.out.println("主线程结束."); // 当任务执行线程已经停止后,主线程结束程序或继续其他操作(这里模拟主线程的下一步动作)
    }
}

在字节码层面 volatile 原理,是通过 flags 表示来实现的
在这里插入图片描述

3.2 synchronized 是什么

synchronized 是 Java 中用于控制多线程并发访问共享资源的一种关键字。它可以确保多个线程在同一时刻只能有一个线程执行某个代码块或方法,从而避免并发问题,如竞态条件。以下是关于 synchronized 的详细介绍:

3.2.1. 基本用法

3.2.1.1 同步方法

直接在方法声明上使用 synchronized 关键字,这样整个方法都在同步块内。例如:

public synchronized void synchronizedMethod() {
    // 同步代码块
}

此时,该方法只能由一个线程在任何时候访问。多个线程调用此方法时,其他线程会被阻塞直到当前线程执行完毕。这种方法的同步锁是当前实例对象(即 this)。如果要同步的是静态方法,锁是类的 Class 对象。

3.2.1.2 同步代码块

可以使用 synchronized(锁对象) 语法创建一个同步代码块。例如:

synchronized (lockObject) {
    // 同步代码块内容
}

此时,只有持有锁对象的线程可以进入该代码块。多个线程可以尝试访问这个同步块,但只有一个能获得锁并执行其中的代码。其他线程会被阻塞直到锁被释放。锁对象可以是任何对象,通常是一个自然存在的共享资源或者一个特定的锁对象。当锁对象是 this 时,相当于同步整个方法。如果锁对象是某个静态变量,那么任何线程调用这段代码都需要获得那个锁才能执行。但一定要确保使用的锁对象不会在方法中频繁变化。如果对象在不同的上下文中表示不同的资源或意义,则可能导致意外的并发问题。

3.2.2. 工作原理

当一个线程进入一个 synchronized 代码块或方法时,它首先尝试获取锁对象对应的内置锁(也称为监视器锁)。如果锁已经被另一个线程持有,那么该线程将会进入等待状态,直到锁被释放(由持有锁的线程退出同步块或调用 wait() 方法)。这种机制确保了同一时刻只有一个线程可以访问同步代码块或方法中的共享资源。一旦线程释放锁(退出同步块或方法),其他等待的线程将有机会尝试获取该锁并执行相应的代码。如果多个线程同时尝试获取同一个对象的锁,它们会被按照某种顺序来串行化地执行同步代码块或方法。值得注意的是,同步控制开销通常比上下文切换的开销要小得多。此外,JVM 会尝试优化锁的获取和释放过程以提高性能。对于复杂的应用场景,还有专门的性能调优和死锁避免策略可用。

3.2.3. 保证原子性和可见性

是的,我的解释是正确的。在 Java 中使用 synchronized 关键字时,确实涉及到内存模型和工作内存与主内存的刷新问题,特别是在多线程环境下。让我们再次详细解释这一点。

当你在 Java 程序中使用 synchronized 块时,这个关键字确保了多个线程对共享资源的同步访问。这是通过确保只有一个线程能够获取到锁来实现的。在这个同步块内,所有的读写操作都是对该锁对象的原子操作。这就意味着当一个线程进入 synchronized 块时,它会获取锁对象,并开始执行同步块内的代码。此时,其他尝试进入该同步块的线程会被阻塞,直到持有锁的线程释放锁为止。

在这个过程中,涉及到内存模型和工作内存与主内存的交互:

  • 互斥访问:当一个线程进入 synchronized 块时,它需要获得锁。只有获得锁的线程才能执行同步块内的代码,从而保证了同一时刻只有一个线程在操作共享变量。
  • 工作内存与主内存的刷新:当一个线程持有锁并执行同步块内的操作时,它会首先从主内存中读取共享资源到工作内存中(即线程的本地缓存)。在工作内存中修改这些数据后,只有当线程释放锁并允许其他线程获取锁时,这些修改才会被写回主内存。这就意味着同步块内的操作保证了在工作内存中对共享数据的操作在所有线程之间是一致的。
  • 缓存一致性:在多线程环境中,缓存一致性是确保多个线程之间共享的数据保持一致性的过程。synchronized 块确保了在任何时候只有一个线程能够修改共享数据,从而避免了缓存不一致的问题。即使其他线程尝试读取或写入共享数据,由于它们被阻塞在锁之外,它们不能看到在工作内存中的修改,除非持有锁的线程释放锁并允许它们获取锁。

简而言之,synchronized 通过确保对共享资源的独占访问来强制工作内存与主内存的刷新,从而保证了缓存一致性。它确保了任何线程在访问或修改共享数据时都是实时的并且一致的。

3.3 synchronized 原理和例子

public class Counter {
     private int count = 0; // 共享资源:计数器

    // synchronized 块用于保护对 count 的并发访问
    public synchronized void increment() {
            count++; // 对共享资源的操作(这里是增加计数器的值)
    }

    public int getCount() {
        return count; // 外部访问共享资源(可能需要同步)
    }

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter(); // 创建 Counter 实例对象
        // 创建多个线程,模拟并发增加计数器的值
        for (int i = 0; i < 10; i++) { // 创建 10 个线程来并发增加计数器的值
            Thread thread = new Thread(() -> {
                for (int j = 0; j < 100; j++) { // 每个线程都尝试增加计数器值多次以模拟并发情况
                    counter.increment(); // 使用 synchronized 块来安全地增加计数器值
                }
            });
            thread.start(); // 启动线程
            thread.join();
        } // 循环结束后,所有线程都已经启动并尝试增加计数器的值。由于使用了 synchronized 块,计数器的值将准确累积而不会发生冲突或重叠增加的情况。可以检查 getCount() 的返回值来验证这一点。
        System.out.println(counter.count);
    }
}

在字节码层面 synchronized 原理

  1. 方法同步: 对于使用synchronized修饰的方法,JVM会在方法的访问修饰符字节码指令之前插入一个特殊的指令,称为“ACC_SYNCHRONIZED”。这个标志告诉JVM该方法需要一个锁来执行。当线程调用这个方法时,它必须先获得对象的内置锁(即实例锁)。持有锁的线程可以在该对象的任何方法上执行任何操作,而不会阻塞其他线程对同一个对象的访问。当方法返回时,锁会被自动释放。

  2. 代码块同步: 对于使用synchronized(obj)修饰的代码块,Java编译器会在该代码块的起始和结束位置生成特定的字节码指令。具体来说,它会使用monitorentermonitorexit指令。monitorenter指令用于获取对象的内部锁,而monitorexit指令用于释放该锁。这两个指令确保了同步代码块中的代码是原子的,并且不会被其他尝试获取同一锁的线程中断。如果在持有锁的过程中发生了异常,需要确保monitorexit指令始终被执行以释放锁。

四、总结

在多线程编程中,线程之间的通信非常重要,它可以实现资源共享、任务分工、同步操作、数据传递和提高性能等功能。为了确保线程之间的通信的正确性和效率,可以使用volatile关键字和synchronized关键字。volatile关键字可以保证可见性和禁止指令重排序优化,而synchronized关键字可以保证对共享资源的同步访问,保证原子性和可见性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Studying-代码随想录训练营day14| 226.翻转二叉树、101.对称二叉树、104.二叉树的最大深度、111.二叉树的最小深度

第十四天&#xff0c;(ง •_•)ง&#x1f4aa;&#x1f4aa;&#xff0c;编程语言&#xff1a;C 目录 226.翻转二叉树 101.对称二叉树 100.相同的树 572.另一个树的子树 104.二叉树的最大深度 559.n叉树的最大深度 111.二叉树的最小深度 总结 226.翻转二叉树 文档讲…

linux 查找和搜索命令

查找传入条件 a:在哪些目录查找 b:查找的内容 find命令: find 目录名 选项 查找条件 find /home/Camera/ -name “*.txt” 解释: /home/Camera/ 指明了查找的路径 -name 表明以名字来查找文件 *.txt 查找名为.txt的文件 grep命令: 在当前目录下查找: grep -n "sample_s…

AI辅助工具革新金融交易编程,引领行业新风尚

今天有伙伴问了我金融方面AI的问题&#xff0c;我来写一下吧 随着人工智能技术的飞速发展&#xff0c;金融交易领域也迎来了新的变革。传统的金融交易编程往往依赖于交易员的专业知识和编程技能&#xff0c;但在人工智能的助力下&#xff0c;自动写代码的AI辅助工具正逐渐崭露…

访问网络 测试没有问题,正式有问题

ping -c 5 -i 1 www.baidu.com 首先检查一下网络&#xff0c;在正式服务器ping 一下要访问的地址。 如果不行&#xff0c;联系网络管理员开通。 笔者因为这个问题浪费了半天时间&#xff0c;一开始都以为是代码问题 以后大家看到这个异常&#xff0c;解决如下&#xff1a; …

对角线法则的由来

目录 一、前言 二、对角线法则 三、行列式的定义 1. 行列式的定义 2. (全)排列 3. 逆序数 四、由全排列逆序数 到 对角线法则规律 ​编辑 五、参考书目 一、前言 仅限于个人理解&#xff0c;对错没有查证。 二、对角线法则 提起对角线法则&#xff0c;我们更倾向于他是…

企业如何选择合适的CRM工具?除Salesforce之外的10大主流选择

对比salesforce&#xff0c;其他10款优秀CRM&#xff1a;纷享销客CRM、Zoho CRM、腾讯企点、销售易、企业微信 (WeCom)、Odoo CR、OroCRM、金蝶、用友CRM、EspoCRM 虽然Salesforce以其全面的功能和强大的市场占有率在海外收获了许多客户&#xff0c;但Salesforce在国内市场的接…

Microsoft AI Day:支持开放合作,普及技术应用,推进行业企业智慧化创新

微软在北京举办以“共创AI创新&#xff0c;智启无限可能”为主题的Microsoft AI Day活动&#xff0c;集中展示了在生成式智能技术加速发展普及的过程中&#xff0c;微软取得的最新技术突破与进展&#xff0c;并同步更新了在Microsoft Build 2024全球开发者大会上发布的一系列Az…

springboot中,将某个函数的日志单独输出的方法

这里写自定义目录标题 背景解决方案解决过程原理 背景 项目中有个节点健康检查扫描功能&#xff0c;每10秒扫描一次节点。 如果节点挂掉&#xff0c;会输出健康检查失败的日志。 测试环境&#xff0c;虽然配置了多个节点&#xff0c;但并没有都启动&#xff0c;所以在扫描的时…

油猴hook+内存爆破

hook方式 说明&#xff1a;来回翻页发现只有请求体的token需要逆向&#xff0c;而这个请求体是在params里&#xff0c;拼接到url里&#xff0c;可以直接用油猴hook url里的关键字token。 正常步骤 hook代码 // UserScript // name hookparams // namespace htt…

前端易遭受的六大安全威胁,以及对应解决策略。

前端遭受安全威胁可能会导致用户隐私泄露、账户被盗用、系统遭受攻击、用户体验受损等严重后果&#xff0c;所有安全防御也成了前端开发者的必须课之一&#xff0c;贝格前端工场带领大家了解下常见的安全威胁。 一、前端开发面临的安全风险 1. 跨站脚本攻击&#xff08;XSS&a…

项目实施经理岗位的工作内容(合集)

项目实施经理岗位的工作内容1 职责&#xff1a; (1)负责协调软件团队对软件产品的研发工作(包括代码开发&#xff0c;测试&#xff0c;部署实施等); (2)引导和解析客户需求&#xff0c;根据产品特点及用户个性化需求制定解决方案&#xff0c;完成客户宣讲等售前技术支持工作; (…

【CT】LeetCode手撕—141. 环形链表

目录 题目1- 思路2- 实现⭐141. 环形链表——题解思路 3- ACM实现 题目 原题连接&#xff1a;141. 环形链表 1- 思路 模式识别 模式1&#xff1a;判断链表的环 ——> 快慢指针 思路 快指针 ——> 走两步慢指针 ——> 走一步判断环&#xff1a;若快慢相遇则有环&a…

聊聊我构建SMoE模型的过程

这篇博客详细讲述了从头开始构建一个稀疏混合专家&#xff08;sparse mixture of experts&#xff09;语言模型的过程。该项目深受 Andrej Karpathy 的 “makemore” 项目启发&#xff0c;并借鉴了许多可重用的组件。与 makemore 类似&#xff0c;makeMoE 也是一种按字符顺序生…

AI与业务的结合 | 使用机器学习预测客户反应,轻松实现市场营销策略优化

01、案例说明 在实际的工作中&#xff0c;除了数据本身需要处理之外&#xff0c;同时也需要对所建立模型进行检验与了解。所以在这个过程之中我们学习2个比较进阶的功能&#xff1a;一个是关于决定属性和结果之间的关系从而判断哪些是更重要的属性&#xff1b;另外一部分则是在…

【CPP】直接选择排序、堆排序

目录 1.选择排序1.1简介1.2代码1.3分析 2.堆排序2.1简介2.2代码2.3分析 1.选择排序 1.1简介 思路&#xff1a;遍历一遍&#xff0c;选出最大值和最小值的下标&#xff0c;然后与第一个和最后一个数字交换位置。 1.2代码 1.3分析 最好复杂度&#xff1a;O(N^2) 最差复杂度&am…

Sui的Fastcrypto加密库刷新速度记录

Sui使用的加密库Fastcrypto打破了许多速度记录&#xff0c;Mysten Labs在基准测试和安全分析中的工作修复了许多安全漏洞&#xff0c;同时通过识别新的优化技巧为创新开辟了道路。 最近在伦敦帝国理工学院举行的国际性能工程会议&#xff08;ICPE&#xff09;基准测试研讨会上…

编译原理:代替LR的MP:2.遇到的问题

用指针加速 MP是multi-pass&#xff0c;多遍分析法&#xff0c;它是从“先乘除后加减”中得来的灵感。在实践中&#xff0c;发现C语言优先级有15级&#xff0c;如果将源代码处理15遍&#xff0c;每一遍都从头开始找&#xff0c;势必很慢。所以&#xff0c;有了用指针加速的想法…

开发者配置项、开发者选项自定义

devOptions.vue源码 <!-- 开发者选项 &#xff08;CtrlAltShiftD&#xff09;--> <template><div :class"$options.name" v-if"visible"><el-dialog:custom-class"sg-el-dialog":append-to-body"true":close-on…

发力采销,京东的“用户关系学”

作者 | 曾响铃 文 | 响铃说 40多岁打扮精致的城市女性&#xff0c;在西藏那曲的偏远农村&#xff0c;坐着藏民的摩托车&#xff0c;行驶在悬崖边的烂泥路上&#xff0c;只因为受顾客的“委托”&#xff0c;要寻找最原生态的藏区某款产品。 30多岁的憨厚中年男性&#xff0c;…

【吊打面试官系列-Mysql面试题】SQL 语言包括哪几部分?每部分都有哪些操作关键字?

大家好&#xff0c;我是锋哥。今天分享关于 【SQL 语言包括哪几部分&#xff1f;每部分都有哪些操作关键字&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; SQL 语言包括哪几部分&#xff1f;每部分都有哪些操作关键字&#xff1f; SQL 语言包括数据定义(DDL)、…