并发编程(6)

指令重排序

指令重排序是指在程序执行过程中,为了提高性能,编译器或处理器会对指令的执行顺序进行重新排列。

指令重排序导致可见性消失

在多线程环境下,每个线程都有自己的工作内存,线程对变量的操作是在工作内存中进行的,而不是直接操作主内存中的变量。当线程将主内存中的变量读取到工作内存后,如果发生指令重排序,可能会导致该线程对变量的修改在其他线程看来不可见。这是因为指令重排序可能会使变量的修改在时间上被推迟,而其他线程在这段时间内读取的仍然是旧值,从而出现可见性问题

示例

public class VisibilityProblem {private static boolean flag = false;private static int data = 0;public static void main(String[] args) throws InterruptedException {// 线程1负责修改flag和data的值Thread thread1 = new Thread(() -> {data = 10;flag = true;});// 线程2在flag为true时,打印data的值Thread thread2 = new Thread(() -> {while (!flag) {// 线程2在此处自旋等待flag变为true}System.out.println("data = " + data);});thread1.start();thread2.start();thread1.join();thread2.join();}
}

按照正常的逻辑,线程 1 先将data赋值为 10,然后将flag设置为true,线程 2 在flag变为true后,应该打印出data的值为 10。然而,由于指令重排序,线程 1 中对dataflag的赋值操作可能会被重排序,导致线程 2 可能先看到flag变为true,而此时data的值可能还没有被更新为 10,从而打印出错误的结果(可能是 0)。

解决方案

  • 使用 volatile 关键字:对共享变量使用volatile关键字修饰,它可以保证变量的内存可见性,即当一个线程修改了volatile变量的值,其他线程能够立即看到这个修改,同时禁止指令重排序,确保volatile变量的读写操作按照程序中的顺序执行。如将上述代码中的flag声明改为private static volatile boolean flag = false;,就可以解决可见性问题。
  • 使用锁机制:通过synchronized关键字或者ReentrantLock等锁机制来保证同一时刻只有一个线程能够访问共享资源,这样可以避免指令重排序带来的可见性问题。因为在获取锁和释放锁的过程中,会有相应的内存屏障来保证内存的可见性和禁止指令重排序。例如,将线程 1 中修改dataflag的代码放在synchronized块中,也能解决可见性问题。

指令重排序解决翻案

虽然因为流水线技术导致了指令重排序产生可能的错误,但它提高了性能。所有我们可以禁止一定数量的指令重排序。比如说有200行代码,我们需要这两行不要指令重排序,按顺序执行,那么我们只设计局部。防止指令重排序就行了。

  • 使用内存屏障(Memory Barrier)

    • 原理:内存屏障是一种指令,它可以阻止处理器在内存屏障之前的指令和之后的指令之间进行重排序,并且确保在执行到内存屏障时,之前所有的写操作都已经完成并对其他处理器可见,之后的读操作将获取到最新的值。
    • 作用:通过在适当的位置插入内存屏障,可以保证多线程环境下内存操作的顺序性和可见性,从而避免因指令重排序导致的问题。例如,在 Java 中,volatile关键字的实现就利用了内存屏障,当对volatile变量进行写操作时,会在写操作之后插入一个写内存屏障,确保写操作对其他线程可见;当对volatile变量进行读操作时,会在读操作之前插入一个读内存屏障,确保读到的是最新的值。
  • 利用原子操作和同步机制

    • 原理:原子操作是指在执行过程中不会被中断的操作,它要么完全执行,要么完全不执行。在多线程环境下,使用原子操作可以保证对共享资源的访问是原子性的,避免了指令重排序和数据竞争问题。同时,同步机制如锁(synchronized关键字、ReentrantLock等)可以保证在同一时刻只有一个线程能够访问被保护的代码块或资源。
    • 作用:在获取锁和释放锁的过程中,会有相应的内存屏障来保证内存的可见性和禁止指令重排序。例如,当一个线程获取锁时,会清空自己的工作内存,从主内存中重新读取共享变量的值;当释放锁时,会将工作内存中的修改刷新到主内存中,确保其他线程能够看到最新的值。
  • 采用线程局部存储(Thread - Local Storage,TLS)

    • 原理:线程局部存储是一种为每个线程提供独立存储空间的机制,每个线程可以在自己的局部存储中存储和访问数据,而不会影响其他线程。
    • 作用:通过将共享变量复制到线程局部存储中,每个线程只操作自己的副本,避免了多线程对共享变量的并发访问,从而消除了指令重排序和数据竞争的可能性。不过,使用线程局部存储需要注意数据的生命周期管理,确保在使用完毕后及时清理,避免内存泄漏。
内存屏障

  1. 类型
    • storeFence():禁止写操作重排序(写屏障)。
    • loadFence():禁止读操作重排序(读屏障)。
    • fullFence():禁止所有内存操作重排序(全屏障)。
  2. 适用场景:在需要保证顺序的两行代码前后插入屏障,确保它们不会被重排序。

指令重排序规则

规则(Load-Load/Load-Store....)上一行代码和下一行代码相邻的两行代码。第一行是XX操作,第二行是XX操作。

常见的处理器都允许Store-Load重排序;常见的处理器都不允许对存在数据依赖的操作做重排序。sparc-TSO和X86拥有相对较强的处理器内存模型,它们仅允许对写-读操作做重排序(因为它们都使用了写缓冲区)。

数据依赖

数据依赖是指两条指令之间存在对同一数据的操作,且后一条指令的执行结果依赖于前一条指令的执行结果。它是指令重排序的核心约束条件之一,决定了指令能否被重新排列。

a = 10;       // 指令1:写入a  
b = a + 5;    // 指令2:读取a的值,依赖指令1的结果  

禁止重排序。若交换指令 1 和指令 2 的顺序,指令 2 将读取到未初始化的 a(或旧值),导致结果错误。

happens-before

是一个核心概念,用于定义多线程环境下操作之间的顺序关系,确保前一个操作的结果对后续操作可见,并禁止不合理的指令重排序。它是判断多线程程序是否正确的重要依据,而非实际执行顺序的 “时间先后”,而是一种 逻辑上的因果关系注意两个操作之间具有 happen before 关系,并不意味着前一个操作必须在后一个操作前执行,它仅仅要求前一个操作执行的结果。对后可见。且前一个操作按照顺序排在第二个操作后面
 

happens-before 的核心规则

JMM 定义了以下 6 条基本规则(另有传递性规则),满足这些规则的操作对,前者的结果对后者可见,且两者之间不会发生指令重排序:

1. 程序顺序规则(Program Order Rule)
  • 同一线程内,按照代码顺序,前面的操作 happens-before 后面的操作。
    int a = 1;   // 操作1 happens-before 操作2
    int b = a;   // 操作2 
    
    单线程内,后续操作必然能看到前面操作的结果(编译器 / 处理器不会重排序破坏真依赖)。
2. 监视器锁规则(Monitor Lock Rule)
  • 解锁操作(unlock)happens-before 后续对同一锁的 加锁操作(lock
    synchronized (lock) {x = 10;  // 写操作,unlock happens-before 后续的 lock
    }  // 解锁
    // 其他线程获取锁后:
    synchronized (lock) {int y = x;  // 能看到 x=10(因 unlock happens-before 此次 lock)
    }
    
    锁的释放会将工作内存的数据刷新到主内存,加锁会清空工作内存并重新读取主内存数据。
3. volatile 变量规则(Volatile Variable Rule)
  • 对 volatile 变量的写操作 happens-before 后续对该变量的 读操作
    volatile int flag = 0;
    // 线程A:
    flag = 1;  // 写volatile,happens-before 线程B的读操作
    // 线程B:
    int i = flag;  // 能看到 flag=1(禁止读写操作重排序,且保证内存可见性)
    

    volatile 通过内存屏障禁止重排序,并强制读写时刷新主内存。
4. 线程启动规则(Thread Start Rule)
  • 主线程中调用 thread.start() happens-before 该线程内的 第一个操作
    Thread thread = new Thread(() -> {x = 10;  // 线程内第一个操作,保证在 thread.start() 之后可见
    });
    thread.start();  // happens-before 线程内的所有操作
    
    启动线程后,线程内的操作必然能看到启动前的可见状态。
5. 线程终止规则(Thread Termination Rule)
  • 线程内的 最后一个操作 happens-before 其他线程通过 thread.join() 返回 或检测到线程终止(如 isAlive() 为 false)。
    Thread thread = new Thread(() -> {x = 10;  // 线程内最后一个操作
    });
    thread.start();
    thread.join();  // join() 返回时,保证能看到线程内 x=10
    System.out.println(x);  // 输出 10
    

    join () 会等待线程执行完毕,确保线程内所有操作的结果对主线程可见。
6. 线程中断规则(Thread Interruption Rule)
  • 对线程的 interrupt() 调用 happens-before 被中断线程检测到中断事件(如 interrupted() 或 isInterrupted() 返回 true)。
    Thread thread = new Thread();
    // 线程A:
    thread.interrupt();  // happens-before 线程B检测到中断
    // 线程B:
    boolean isInterrupted = thread.isInterrupted();  // 能正确检测到中断
    
7. 对象终结规则(Finalizer Rule)
  • 对象的 构造函数执行完毕 happens-before 其 finalize() 方法的开始。

传递性规则(Transitivity)

如果操作 A happens-before 操作 B,且操作 B happens-before 操作 C,则 操作 A happens-before 操作 C

// 规则组合示例:
// 1. 线程1解锁锁 L(A happens-before B)
// 2. 线程2获取锁 L(B happens-before C)
// 3. 线程2读取变量 x(C happens-before D)
// 传递后:线程1对x的修改(在解锁前)happens-before 线程2对x的读取(D)

happens-before 与指令重排序的关系

  • 保证可见性:满足 happens-before 的操作对,前者的结果对后者可见(无需担心缓存未刷新或重排序导致的旧值)。
  • 限制重排序范围:JVM 允许编译器 / 处理器对不违反 happens-before 规则的操作进行重排序,以优化性能。
    • 例如:无 happens-before 关系的操作(如不同线程的无依赖操作),可能被重排序;
    • 有 happens-before 关系的操作,其顺序在内存语义上不可被打破。

happen before 传递性

如果 操作 A happens-before 操作 B,且 操作 B happens-before 操作 C,则 操作 A happens-before 操作 C
传递性的本质是将多个独立的 happens-before 关系 “串联” 起来,形成更长的因果链,确保最终的操作结果可见性。

public class TransitivityExample {static int x = 0;static final Object lock = new Object();// 线程1:修改x并释放锁static class Thread1 extends Thread {public void run() {synchronized (lock) { // 加锁1(操作A)x = 10;         // 写x(操作B)} // 释放锁(操作C)}}// 线程2:获取锁并读取xstatic class Thread2 extends Thread {public void run() {synchronized (lock) { // 加锁2(操作D)int y = x;      // 读x(操作E)System.out.println(y); // 输出10}}}public static void main(String[] args) throws InterruptedException {Thread1 t1 = new Thread1();Thread2 t2 = new Thread2();t1.start();t2.start();t1.join();t2.join();}
}
  • 基本规则应用
    1. 线程 1 内:操作 B(写 x)happens-before 操作 C(释放锁)(程序顺序规则)
    2. 锁规则:操作 C(释放锁)happens-before 操作 D(获取锁)(监视器锁规则)
    3. 线程 2 内:操作 D(获取锁)happens-before 操作 E(读 x)(程序顺序规则)
  • 传递性推导
    操作 B → 操作 C → 操作 D → 操作 E,通过传递性,操作 B happens-before 操作 E,确保线程 2 读取到 x=10。

重排序

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间 就存在数据依赖性。这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。

数据依赖性的定义与条件

两个操作访问同一个变量,且至少有一个操作是写操作时,这两个操作之间存在数据依赖性。具体包括以下三种场景(均满足 “至少一个写操作”):

  1. 真依赖(写→读):前一个操作写变量,后一个操作读该变量(如 a=1; b=a;)。
  2. 反依赖(读→写):前一个操作读变量,后一个操作写该变量(如 b=a; a=1;)。
  3. 输出依赖(写→写):两个操作都写同一个变量(如 a=1; a=2;)。

关键约束

  • 数据依赖性仅针对 单个线程内的操作序列 或 单个处理器中执行的指令序列
  • 编译器和处理器只考虑单线程内的数据依赖跨线程、跨处理器的数据依赖被忽略(即不保证多线程间的操作顺序和可见性)。

数据依赖性对指令重排序的影响(单线程 vs 多线程)

1. 单线程场景
  • 编译器和处理器 必须遵守数据依赖性,不会对存在数据依赖的操作进行重排序(尤其是真依赖)。
    • 例如:a=1; b=a+1; 中,真依赖存在,重排序会改变结果,因此禁止重排序。
    • 反依赖和输出依赖允许重排序,但通过寄存器重命名等技术保证单线程结果正确性(如 a=1; a=2; 重排序为 a=2; a=1; 无意义,但最终结果以最后一次写入为准)。
2. 多线程场景
  • 跨线程的数据依赖性不被编译器和处理器考虑!即使两个线程操作同一变量(且至少一个是写操作),编译器 / 处理器可能对跨线程的操作进行重排序,导致以下问题:
    • 可见性问题:线程 A 写入变量后,线程 B 可能读取到旧值(因未刷新主内存或指令重排序)。
    • 伪数据依赖问题:跨线程的操作看似无依赖(如线程 A 写 x 和线程 B 写 y),但实际可能通过共享变量隐含依赖(如依赖 x 和 y 的写入顺序)。

顺序一致性

顺序一致性内存模型是一个理论参考模型,在设计的时候,处理器的内存模型和编程语 言的内存模型都会以顺序一致性内存模型作为参照。就是同步操作。

Java 中 64 位变量(long/double)写操作的原子性问题及多线程风险

核心问题
  1. Java 内存模型(JVM)的规定

    • 对 64 位的longdouble类型变量的写操作不保证原子性(读操作同理,除非使用volatile)。
    • 即:写一个long变量(64 位)可能被拆分为 两次 32 位的写操作(高 32 位和低 32 位)。
  2. 多线程场景下的风险

    • 若两个线程同时修改同一个long变量,可能出现 “半更新” 现象
      • 线程 A 写入前 32 位,尚未写入后 32 位时,线程 B 被调度,写入后 32 位,最终变量值为线程 A 的前 32 位 + 线程 B 的后 32 位(数据错位)。
      • 或线程 A 的写操作未完成时,线程 B 读取变量,得到前 32 位是新值、后 32 位是旧值的 “拼凑结果”。
  3. 底层硬件原因

    • CPU 内存总线宽度不足(如 32 位总线):传输 64 位数据需分两次完成,无法保证一次性写入。
    • 即使在 64 位处理器上,Java 规范也未强制要求原子性(允许平台优化),导致跨平台不一致性。
问题本质
  • 原子性缺失:64 位数据的写操作被拆分为两次独立的 32 位操作,两次操作之间可能发生线程切换,导致数据不一致。
  • 可见性与顺序性问题:即使不考虑线程切换,非原子写操作也可能因指令重排序或缓存延迟,让其他线程看到中间状态。
解决方案
  1. 使用volatile关键字

    • volatile longvolatile double的写操作,JVM 强制保证原子性(底层通过内存屏障实现)。
    • 示例:
      private volatile long value; // 写操作原子性有保障
      
  2. 使用原子类AtomicLong(或AtomicReference<Long>

    • AtomicLong提供get()set()compareAndSet()原子操作方法,确保 64 位数据的读写原子性。
    • 示例:
      private AtomicLong value = new AtomicLong(0);
      value.set(100L); // 原子写
      long v = value.get(); // 原子读
      
  3. 使用同步机制(synchronized或显式锁)

    • 通过加锁保证同一时刻只有一个线程执行写操作,避免线程切换导致的半更新问题。
    • 示例:
      private long value;
      private final Object lock = new Object();public void setValue(long v) {synchronized (lock) {value = v; // 加锁后写操作具有原子性}
      }
      
总结建议
  • 优先使用AtomicLong:比volatile更灵活(支持原子更新操作),比synchronized性能更好(无锁或轻量级锁实现)。
  • 避免依赖平台特性:即使某些 64 位处理器保证long写原子性,Java 规范未强制要求,必须通过显式机制(volatile/ 原子类 / 锁)保证跨平台一致性。
  • 理解原子性边界:原子性仅保证单次操作的完整性,复合操作(如 “先读再写”)仍需额外同步(如 CAS 或锁)。

事务 

事务是一个批次的操作,要么都成功,要么都失败的意思。一个事务可能会有很多步骤,这里关键是总线会同步,总线会同步试图并发使用总线的事务。竞争总线的这些事物会排队在一个处理器的执行总线事务时间,总线会禁止其他的处理器和 IO 设备执行内存的读写,也就说呢,其中一个使用总线的时候,其他就得等着。是物理信号不允许。
总线事务。总线所有的步骤没处理完,我不会释放总线我们最怕的什么呢?我们最怕的,比如说我们对内层操作有两步,执行完一步的时候中断了其他的线程来执行了。我没处理完。我一直站着走路线,其他的用不了。

volatile特性

volatile阻止指定重排序,它能够阻止重排序,它保障线程可见性就是它能保证了你此时此刻能读到最准确的。
它修饰的变量在后边被操作的过程中读或者写的前后受到影响(上下两行)。离得远的行数不受影响。

单例模式

关键设计点

  1. 私有构造函数:防止外部通过new创建实例,确保单例性。

  2. 静态 volatile 变量

    • volatile关键字确保变量的写操作先行发生于后续的读操作(禁止指令重排序),避免其他线程看到 "半初始化" 状态的对象。
    • 在 JDK 5 及以后版本,volatile提供了内存屏障功能,保证初始化对象的过程不会被重排序。
  3. 双重检查机制

    • 第一次检查(无锁):多数情况下实例已经创建,直接返回,避免进入同步块,提高性能。
    • 同步块:仅当第一次检查发现实例为 null 时才进入,确保只有一个线程创建实例。
    • 第二次检查(同步块内):防止多个线程同时通过第一次检查后,重复创建实例。

为什么需要 volatile?

volatile关键字的作用是禁止指令重排序,确保初始化对象的过程按顺序完成:

  1. 分配内存空间
  2. 初始化对象
  3. 将引用指向内存空间

如果没有volatile,可能发生指令重排序,顺序变为:1→3→2。当线程 A 执行了 1 和 3 但还未执行 2 时,线程 B 检查singleton不为 null,直接返回未完全初始化的对象,导致错误。

优点

  • 线程安全:通过双重检查和同步机制,确保只有一个实例被创建。
  • 高效性:大多数情况下无需进入同步块,性能开销小。
  • 延迟加载:实例在第一次使用时才被创建,避免资源浪费。

构造方法,私有无法通过它 new1个对象出来 。构造方法私有无法通过,无法new出对象。没有对象怎么调方法,没有对象能调静态方法,所以方法必须是静态。

方法为什么也是静态的,因为静态方法只能对静态变量进行操作,静态方法无法对非静态变量进行操作。
这加上锁的意思呢?就是说。创建对象的时候,只能有一个对它进行操作。多线程操作的时候,只能有一个线程竞争所对它进行操作。

所以我们用volatile它的时候才加锁,用完之后这个锁就不存在了,因为代码就进不来了,其他线程对吧,调这个方法直接 return 了,只能同时拷贝一个方法的。

Volatile 内存语义实现

概念含义 :内存语义的实现即探讨 volatile 如何设计、需遵循的规则及具备的功能。

重排序规则 :不同代码行的普通变量与 volatile 变量的读写操作组合有不同重排序规则,如普通变量读写与 volatile 写、volatile 读与其他代码行、volatile 写与普通变量写或 volatile 写操作等搭配,9 种组合中阻止 6 种指令重排序,影响范围为前后两行。

屏障类型 :有写读、读写等屏障,分别表示不允许特定两个操作指令重排序,最终优化只需一个写读屏障。

对变量操作定义 :根据变量在等号两侧及是否被 volatile 修饰确定是读操作还是写操作,若一行中有 volatile 则以其为主,两侧都是则读写都在。

与普通变量重排序限制 :旧内存模型允许 long 变量与普通变量重排序,新规则为提供轻量级线程间通信机制,禁止 volatile 变量与普通变量重排序,因重排序可能破坏语义。

功能性能特点 :保证单个变量读写原子性,功能不如锁强大,如 cyclic 锁更霸道,volatile 允许同时读,锁不允许;但 volatile 在可伸缩和执行性能上有优势,相对轻量级。

锁的内存语义 :

synchronize 相关 :锁的释放获取存在 happens-before 关系,即加锁前上一个锁需已释放;线程获取锁时将本地内存设置无效,从主内存读取共享变量,释放锁时将本地内存共享变量刷新到主内存。

底层通知机制 :线程竞争锁,获取成功的线程释放锁时通知阻塞队列所有线程竞争进入就绪队列,并非单独通知某一线程。

公平锁(Fair Lock)非公平锁(Nonfair Lock)

特性公平锁非公平锁
获取锁顺序严格按照等待队列顺序(先到先得)不保证顺序,允许抢占(可能插队)
等待队列必须检查队列头部是否有前驱节点直接尝试抢占,不检查队列
线程饥饿较少发生(按顺序获取)可能发生(新线程频繁抢占导致旧线程等待)
上下文切换更多(每次唤醒队列头部线程)更少(可能直接由刚释放锁的线程重新获取)
吞吐量较低(频繁线程挂起 / 恢复)较高(减少上下文切换开销)
适用场景需严格公平(如资源分配、避免饥饿)追求性能(大多数并发场景)

 

AQS

state 是指加锁次数 ,非公屏锁性能更好,注意非公平性能更好,公屏锁性能不好,公平锁和非公平锁,哪个是主流呢?非公平所是主流性能更好,性能更好。,任务时间短的先执行,你很快执行完了,你执行完之后我下次不用选你了,我性能更好了
我释放锁之后呢,我会轮流去通知这里边队列,比如说这里边有十个线程,我十个线程的话呢,我并不是只通知第一个线程。
我是都同志,我都通知你第一个如果竞争成功了,你去加锁,如果你不是第一个竞争成功的,你不加锁。
这样浪费 CPU 了,就是你通知的时候属于全都通知的,通知的都是属于全都通知,但是呢,你只有第一个竞争成功的时候才加锁其他的优先竞争成功了,属于白竞争。
会判定你是不是第一个不是第一个的话?你就再歇回去。白白浪费 CPU 性能。而非公平的话呢,就是说。我在队列一边有先后顺序我会按照先后顺序

获取当前线程

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

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

相关文章

鸿蒙仓颉开发语言实战教程:页面跳转和传参

前两天分别实现了商城应用的首页和商品详情页面&#xff0c;今天要分享新的内容&#xff0c;就是这两个页面之间的相互跳转和传递参数。 首先我们需要两个页面。如果你的项目中还没有第二个页面&#xff0c;可以右键cangjie文件夹新建仓颉文件&#xff1a; 新建的文件里面没什…

Java 学习笔记:注解、泛型与 IO 流

目录 课程目标 Java 注解(Annotation) 1. 概念与作用 2. 自定义注解示例 3. JDK 内置注解 4.注释 Java 泛型(Generics) 1. 基本语法 2. 通配符与上下限 3. 常见应用场景 Java IO 流 1. 流的分类1.File文件类 2. 字节流与字符流 3. 经典示例:文件拷贝 总结与…

git仓库代码操作

1、从gitee下载代码提交到本地github仓库&#xff0c;保留提交记录 # 查看当前分支 git branch# 查看当前远程仓库 git remote -v# 确保所有更改已提交 git add . git commit -m "准备提交到GitLab"# 添加GitLab远程仓库 git remote add gitlab https://gitlab.com/…

Thinkphp6使用token+Validate验证防止表单重复提交

htm页面加 <input type"hidden" name"__token__" value"{:token()}" /> Validate 官方文档 ThinkPHP官方手册

Mcu_Bsdiff_Upgrade

系统架构 概述 MCU BSDiff 升级系统通过使用二进制差分技术&#xff0c;提供了一种在资源受限的微控制器上进行高效固件更新的机制。系统不传输和存储完整的固件映像&#xff0c;而是只处理固件版本之间的差异&#xff0c;从而显著缩小更新包并降低带宽要求。 该架构遵循一个…

Spring Boot微服务架构(四):微服务的划分原则

微服务划分原则&#xff08;CRM系统案例说明&#xff09; 一、微服务划分的核心原则 单一职责原则&#xff08;SRP&#xff09; 每个微服务只负责一个明确的业务功能服务边界清晰&#xff0c;避免功能混杂便于独立开发、测试和部署 业务领域驱动设计&#xff08;DDD&#xff0…

基于CNN卷积神经网络的带频偏QPSK调制信号检测识别算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2024b 3.部分核心程序 &#xff08;完整版代码包含详细中文注释和操作步骤视频&#xff09…

从机械应答到深度交互,移远通信如何让机器人“灵魂觉醒”?

你是否还在因机器人的“答非所问”而无奈&#xff0c;为它们的“反应慢半拍”而抓狂&#xff1f;别慌&#xff01;一场引领机器人实现“灵魂觉醒”的技术革命&#xff0c;正如同暗夜中悄然绽放的繁星&#xff0c;彻底颠覆人们对机器人的传统认知。 5月20日&#xff0c;移远通信…

软件的技术架构、应用架构、业务架构、数据架构、部署架构

一、各架构定义 1. 技术架构&#xff08;Technical Architecture&#xff09; 定义&#xff1a;技术架构关注的是支撑系统运行的底层技术基础设施和软件平台&#xff0c;包括硬件、操作系统、中间件、编程语言、框架、数据库管理系统等技术组件的选择和组合方式。它描述了系统…

HTML-前端

目录 开始学习HTML 什么是 HTML? 剖析一个 HTML 元素 嵌套元素 块级元素和内联元素 空元素 属性 为元素添加属性 布尔属性 省略包围属性值的引号 使用单引号还是双引号&#xff1f; 剖析 HTML 文档 HTML 中的空白 实体引用&#xff1a;在 HTML 中包含特殊字符 HT…

多态的总结

什么是多态&#xff1f; 答&#xff1a;多态是多种形态&#xff0c;是为了完成某种行为时&#xff0c;不同对象会产生不同的形态&#xff08;结合车票例子解释&#xff09; 2. 什么是重载、重写(覆盖)、重定义(隐藏)&#xff1f; 答&#xff1a;重载的条件是&#xff1a;在同一…

VBA 读取指定范围内的单元格数据,生成csv文件

目录 一. 需求二. 宏代码三. 添加按钮 一. 需求 ⏹有如下表格&#xff0c;现在想在Excel中添加一个按钮 点击按钮之后&#xff0c;读取该表格中的数据&#xff0c;生成csv文件将csv文件输出到和Excel同级目录 二. 宏代码 Application.PathSeparator&#xff1a;路径分隔符Cr…

【Code Agent Benchmark】论文分享No.15:TAU-Bench

论文名称&#xff1a;τ-bench: A Benchmark for Tool-Agent-User Interaction in Real-World Domains 论文&#xff1a;https://arxiv.org/abs/2406.12045 机构&#xff1a;Sierra Github 链接&#xff1a;https://github.com/sierra-research/tau-bench# 简介 相比于Swe-ben…

Linux下 使用 SSH 完成 Git 绑定 GitHub

文章目录 1、检查 SSH2、生成 SSH key3、添加 SSH key4、验证绑定是否成功 1、检查 SSH Git Bash 中输入ssh命令&#xff0c;查看本机是否安装 SSH&#xff1a; 2、生成 SSH key &#xff08;1&#xff09;输入 ssh-keygen -t rsa 命令&#xff0c;表示我们指定 RSA 算法生…

Java 8 Stream 流操作全解析

文章目录 **一、Stream 流简介****二、Stream 流核心操作****1. 创建 Stream****2. 中间操作&#xff08;Intermediate Operations&#xff09;****filter(Predicate<T>)&#xff1a;过滤数据****1. 简单条件过滤****2. 多条件组合****3. 过滤对象集合****4. 过滤 null 值…

Java——设计模式(Design Pattern)

设计模式&#xff08;Design Pattern&#xff09;是软件开发中针对常见问题的经典解决方案&#xff0c;由 GoF&#xff08;Gang of Four&#xff09;在《设计模式&#xff1a;可复用面向对象软件的基础》一书中归纳为23 种模式&#xff0c;分为三大类&#xff1a;创建型模式、结…

python语法学习

1.python的类的定义 class Memory_Manager: 2.__init__ 方法 __init__ 是类的构造方法&#xff0c;用于初始化类的实例。 self 是类实例的引用&#xff0c;用于访问类的属性和方法。 3.方法定义 类中的方法是类的功能实现&#xff0c;通过 def 定义。 4.if __name__ __ma…

如何屏蔽mac电脑更新提醒,禁止系统更新(最新有效方法)

每次打开Mac电脑时&#xff0c;频繁的系统更新提醒可能会对我们的工作和使用体验造成干扰。为了屏蔽这些更新提醒并禁止系统自动更新&#xff0c;我们可以通过修改Hosts文件来实现。以下是详细步骤和方法&#xff0c;帮助你彻底屏蔽macOS的更新提醒。 系统关闭了自动更新也是…

windows10重装ssh无法下载

问题 windows10重装之后&#xff0c;ssh每次都是由于连接的是流量计数的网络无法下载。 解决方法 https://www.cnblogs.com/zhg1016/p/17353348.html

解决 cursor 中不能进入 conda 虚拟环境

【问题】 遇到一个小问题&#xff0c;我创建的conda 环境在 cmd、powershell中都可以激活&#xff0c;但在pycharm、cursor中却不能激活&#xff1f; 看图 cmd中正常&#xff1a; cursor中不正常&#xff1a; 【解决方法】 cursor 中&#xff0c;打开终端&#xff0c;输入&a…