多线程篇-4--重点概念1(volatile,Synchronized,内存屏障,MESI协议)
一、volatile
(1)、简述
volatile是java提供的一个关键字,英文意思为不稳定的。
可以保障被声明对象的可见性和一定程度上的有序性,但不能保证操作的原子性。
当一个变量被声明为volatile时,意味着该变量的值会直接从主内存中读取,并且对该变量的任何写操作都会立即写回主内存,并通知给其他引用该变量的线程。
(2)、volatile不保证原子性怎么理解?
volatile关键字只能确保单个步骤变量的读写操作是原子的,但不能保证复合操作的原子性。例如,i++操作实际上是三个步骤(读取、加1、写回),即使i被声明为volatile,这三个步骤仍然是可以被中断的。
简单说:在多线程场景下,volatile变量被执行的操作是多步骤的(如:i++),其他线程也刚好读i这个数据,可能会出现读写不一致的问题。
(3)、不保证原子性,为啥还要用它?
volatile是轻量级的同步机制,对性能的影响比synchronized小。(性能还不错)
典型的用法:检查某个状态标记以判断是否退出循环。(主要用于循环判断退出,这种场景还不错,不要用过复杂场景)
那为什么我们不直接用synchorized,lock锁?它们既可以保证可见性,又能保证原子性啊?
因为synchorized和lock是排他锁(悲观锁),如果有多个线程需要访问这个变量,将会发生竞争,只有一个线程可以访问这个变量,其他线程被阻塞了,会影响程序的性能。(多线程时性能差啊)
(4)、使用volatile的条件
1、对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。(如:用于读多写少的场景,或写时避免出现i++多步骤操作的场景)
2、该变量不会与其他的状态一起纳入不变性条件中。(如果volatile值用于判断,且对结果不会产生改变的话,对程序就没有了意义)
3、在访问变量时不需要加锁。(都使用锁了,还要这个干嘛)
(5)、怎么理解volatile是有序性的呢?
volatile关键字可以防止指令重排序。编译器和处理器在编译和执行过程中可能会对指令进行重排序以优化性能,但这些重排序可能会导致并发问题。volatile变量的读写操作会产生内存屏障(Memory Barrier),确保在读取或写入volatile变量之前/之后的指令不会被重排序。
(6)、内存屏障
内存屏障(Memory Barrier,也称为内存栅栏或内存 fence)是计算机体系结构和编程语言中的一种同步机制,用于确保特定的内存操作按指定的顺序执行。内存屏障主要用于防止编译器和处理器对指令进行重排序,从而保证程序的正确性和一致性。
在附录模块,在重点介绍下内存屏障。
(7)、线程如何感应到volatile变量更新?
1、缓存一致性理解
当多个CPU持有的变量副本都来自同一个主内存的拷贝时,如果某个CPU改了这个主内存变量值后,其他CPU并不知道,那拷贝的内存将会和主内存不一致,这就是缓存不一致。如果其他CPU都知道的话,那拷贝的内存将会和主内存一致,也就是缓存一致了。
如下图所示,CPU2 偷偷将num修改为2,内存中num也被修改为2,但是CPU1和CPU3并不知道num值变了。
2、怎么保证缓存一致性呢?
实现缓存一致性的原理就是MESI协议和窥探(snooping)协议,这个在附录中在详细说明。
二、synchronized
(1)、概述
Synchronized是Java中用于实现线程同步的关键字。它通过确保同一时间只有一个线程可以访问某个资源来避免多线程环境下的竞态条件和数据不一致问题。Synchronized主要通过锁机制来实现这一点。
(2)、实现原理
1、锁机制
- Synchronized关键字会在进入同步代码块或同步方法时获取锁,在退出同步代码块或同步方法时释放锁。
- 锁可以是对象锁或类锁(静态方法锁)。
即:代码运行时上锁,结束后释放锁,保证同一时刻只能让一个线程运行。
2、锁分类
(1)、对象锁
- 对于非静态方法或同步代码块,锁是对象的实例锁。
- 每个对象都有一个内置的锁(也称为监视器锁或monitor lock)。
- 同一时间只能有一个线程可以持有该对象的锁。
即:一个对象中存在多个Synchronized的方法,多个线程访问该类的不同的同步方法时,也是只有一个线程会运行,其他线程被阻塞,因为同步方法的锁就是对象本身。
代码示例:
public class MyClass {private int count = 0;public synchronized void increment() {count++;}public static void main(String[] args) {MyClass obj = new MyClass();Thread t1 = new Thread(() -> obj.increment());Thread t2 = new Thread(() -> obj.increment());t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(obj.count); // 输出 2}
}
(2)、类锁
- 对于静态方法或同步代码块,锁是类的锁。
- 类锁是针对类的Class对象的锁。
- 同一时间只能有一个线程可以持有该类的锁。
即:被static修饰的synchronized 方法,所有使用该方法的线程,都会被上锁
代码示例:
public class MyClass {private static int count = 0;public static synchronized void increment() {count++;}public static void main(String[] args) {Thread t1 = new Thread(() -> MyClass.increment());Thread t2 = new Thread(() -> MyClass.increment());t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(MyClass.count); // 输出 2}
}
3、锁的获取和释放
- 当一个线程尝试进入一个被synchronized保护的代码块或方法时,它会尝试获取锁。
- 如果锁已被其他线程持有,当前线程会被阻塞,直到锁可用。
- 当线程退出同步代码块或方法时,会自动释放锁。
(3)、适用场景
1、资源共享
- 当多个线程需要访问和修改同一个资源(如变量、对象或文件)时,使用synchronized可以确保数据的一致性和完整性。
- 例如,多个线程对同一个计数器进行增减操作。
2、线程安全的单例模式 - 使用synchronized可以确保在多线程环境下,单例模式的实例只会被创建一次。
- 例如,懒汉式单例模式。
3、避免竞态条件 - 当多个线程对同一个变量进行读写操作时,可能会导致竞态条件,使用synchronized可以避免这种情况。
- 例如,银行账户的转账操作。
4、线程间通信 - synchronized可以用于实现线程间的通信,通过等待和通知机制来协调线程的行为。
- 例如,生产者-消费者模型。
(4)、产生的问题
1、性能开销
- synchronized会带来一定的性能开销,因为它会导致线程阻塞和唤醒。在高并发场景下,可以考虑使用更细粒度的锁或无锁算法来提高性能。如,使用
ReentrantLock
或Atomic
类。
2、死锁 - 使用
synchronized
时需要注意避免死锁。死锁通常发生在多个线程互相等待对方释放锁的情况下。 - 例如,两个线程分别持有不同的锁,但又试图获取对方持有的锁。
3、锁的粒度 - 尽量使用细粒度的锁,只锁定必要的部分,以减少锁的竞争和提高并发性能。
- 例如,上锁的代码越少越好,只锁最重点的部分;锁定一个特定的对象而不是整个方法。
(5)、Synchronized总结
Synchronized是Java中实现线程同步的一种简单而强大的机制。它通过锁机制确保在同一时间只有一个线程可以访问被保护的资源,适用于多种需要线程安全的场景。然而,在高并发场景下,需要权衡性能和安全性,选择合适的同步机制。
(6)、Synchronized实现内存模型三大特性
1、实现原子性
实现原理
- synchronized关键字确保同一时间只有一个线程可以进入被同步的代码块或方法。
- 这样可以防止多个线程同时执行相同的代码段,从而确保操作的原子性。
代码示例:
public class AtomicityExample {private int counter = 0;public synchronized void increment() {int temp = counter; // 读取temp += 1; // 修改counter = temp; // 写回}public static void main(String[] args) {AtomicityExample example = new AtomicityExample();Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {example.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {example.increment();}});t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Final counter value: " + example.counter); // 输出 2000}
}
2、实现可见性
实现原理
- 当一个线程释放锁时,它会将本地内存中的所有变量值刷新回主内存。
- 当另一个线程获取同一个锁时,它会从主内存中读取最新的变量值。
即:调用synchronized 方法时,对象锁(即如下的example实例)会被从主内存加载后,完成上锁;方法执行结束,会将对象锁重新刷回到主内存中,在释放锁。
代码示例
public class VisibilityExample {private int value;public synchronized void writeValue(int newValue) {value = newValue; // 写操作}public synchronized int readValue() {return value; // 读操作}public static void main(String[] args) {VisibilityExample example = new VisibilityExample(); // 对象锁,synchronized执行前后都会进行一次线程变量副本和主内存的数据同步。Thread writer = new Thread(() -> {example.writeValue(10);});Thread reader = new Thread(() -> {while (example.readValue() == 0) {// 等待直到value被修改}System.out.println("Value is now: " + example.readValue());});writer.start();reader.start();}
}
解释:
writeValue
方法和readValue
方法都被声明为synchronized
,这意味着它们在执行时会获取同一个对象锁。- 当
writer
线程调用writeValue
方法并修改value
时,它会释放锁并将value
的最新值刷新回主内存。 - 当
reader
线程调用readValue
方法时,它会获取锁并从主内存中读取最新的value
值。
3、实现有序性
实现原理
- synchronized关键字通过内存屏障(Memory Barrier)确保指令不会被重排序。
- 当一个线程释放锁时,它会插入一个内存屏障,确保之前的所有写操作都已完成并可见。
- 当另一个线程获取锁时,它也会插入一个内存屏障,确保之后的所有读操作都能看到之前的写操作。
代码示例
public class OrderingExample {private int a = 0;private boolean flag = false;public synchronized void writer() {a = 1; // 写操作flag = true; // 设置标志}public synchronized void reader() {if (flag) {System.out.println("a = " + a); // 应该总是输出 a = 1}}public static void main(String[] args) {OrderingExample example = new OrderingExample();Thread writerThread = new Thread(() -> {example.writer();});Thread readerThread = new Thread(() -> {example.reader();});writerThread.start();readerThread.start();try {writerThread.join();readerThread.join();} catch (InterruptedException e) {e.printStackTrace();}}
}
解释:
writer
方法和reader
方法都被声明为synchronized
,确保它们在同一时间只能由一个线程执行。writer
方法先写入a
,然后设置flag
。reader
方法在检查flag
时,如果flag
为true
,则读取a
的值。- 由于
synchronized
确保了有序性,reader
方法在读取a
时一定会看到a
的最新值(即1)。
三、附录
附1:内存屏障
1、概念
内存屏障(Memory Barrier,也称为内存栅栏或内存 fence)是计算机体系结构和编程语言中的一种同步机制,用于确保特定的内存操作按指定的顺序执行。内存屏障主要用于防止编译器和处理器对指令进行重排序,从而保证程序的正确性和一致性。
2、内存屏障的作用
(1)、防止指令重排序
编译器和处理器为了优化性能,可能会对指令进行重排序。内存屏障可以确保在屏障之前的指令在屏障之后的指令之前完成。
(2)、确保内存可见性
内存屏障可以确保一个线程对内存的修改对其他线程是可见的。
3、内存屏障的分类
可分为两大类:读屏障和写屏障
4、写屏障(Store Barrier)
(1)、简述
当一个线程写入一个volatile变量时,Java内存模型会在写操作之后插入一个写屏障(Store Barrier)。这个屏障确保了所有在此之前发生的写操作在写入volatile变量之前完成,并且这些写操作的结果对其他线程是可见的。
(2)、实现原理
1、确保之前的写操作完成:
- 在写入volatile变量之前,所有之前的写操作必须完成。
- 编译器和处理器不能将这些写操作重排序到写入volatile变量之后。
2、刷新到主内存:
- 写入volatile变量时,该变量的值会立即刷新到主内存中,而不是仅保存在线程的工作内存中。
- 这确保了其他线程在读取这个volatile变量时,能够看到最新的值。
(3)、代码示例
public class VolatileWriteBarrierExample {private volatile boolean flag = false;private int data = 0;public void writer() {data = 42; // 写入普通变量flag = true; // 写入volatile变量,插入Store Barrier}
}
在这个例子中:
data = 42
是一个普通的写操作。flag = true
是一个写入volatile
变量的操作,会插入一个写屏障。- 写屏障确保了在写入
flag
之前,data = 42
已经完成,并且data
的值已经刷新到主内存中。
5、读屏障(Load Barrier)
(1)、简述
当一个线程读取一个volatile变量时,Java内存模型会在读操作之后插入一个读屏障(Load Barrier)。这个屏障确保了所有在此之后的读操作在读取完成volatile变量之后开始,并且能够看到最新的值。
(2)、实现原理
1、确保之前的读操作完成:
- 在读取volatile变量之前,所有之前的读操作必须完成。
- 编译器和处理器不能将这些读操作重排序到读取volatile变量之后。
2、从主内存中读取:
- 读取volatile变量时,该变量的值会直接从主内存中读取,而不是使用线程工作内存中的副本。
- 这确保了读取到的是最新的值。
3、确保后续的读操作看到最新的值:
- 在读取volatile变量之后,所有后续的读操作必须看到最新的值。
- 编译器和处理器不能将这些读操作重排序到读取volatile变量之前。
(3)、代码示例
public class VolatileReadBarrierExample {private volatile boolean flag = false;private int data = 0;public void reader() {if (flag) { // 读取volatile变量,插入Load Barrierint localData = data; // 读取普通变量// 使用localData}..这里的操作也会等flag获取到值之后才会执行}
}
在这个例子中:
if (flag)
是一个读取volatile
变量的操作,会插入一个读屏障。- 读屏障确保了在读取
flag
之前,所有之前的读操作已经完成,并且flag
的值是从主内存中读取的。 - 在读取
flag
之后,int localData = data
会看到最新的data
值,因为读屏障确保了data
的值已经是最新的。
6、读写屏障总结
(1)、写屏障(Store Barrier)
确保在写入volatile变量之前,所有之前的写操作已经完成,并且这些写操作的结果已经刷新到主内存中。
(2)、读屏障(Load Barrier)
确保在读取volatile变量之后,所有后续的读操作能够看到最新的值,并且这些读操作不会被重排序到读取volatile变量之前。
通过这些屏障,volatile关键字确保了多线程环境下的内存可见性和有序性,从而避免了并发问题。
附2:MESI协议
(1)、概述
MESI协议是一种广泛用于多处理器系统中的缓存一致性协议。它的名字来源于四种缓存行状态:Modified(已修改)、Exclusive(独占)、Shared(共享)、Invalid(无效)。MESI协议通过维护这些状态来确保多个处理器核心之间的缓存数据一致性。
(2)、四种状态解释
1、Modified (已修改)
- 当一个缓存行处于Modified状态时,表示该缓存行的数据已经被修改过,但尚未写回主内存。此时,只有当前处理器持有该缓存行的副本,并且它是唯一的、最新的版本。
- Modified状态下的缓存行被称为“脏”(dirty),因为其内容与主内存中的内容不一致。
2、Exclusive (独占)
- Exclusive状态表示当前处理器独占了该缓存行,但没有对其进行任何修改。这意味着当前处理器是唯一拥有该缓存行副本的处理器,但它还没有对该数据做出任何更改。
- Exclusive状态下的缓存行在被处理器读取后,如果处理器开始对该数据进行写操作,该缓存行将转变为Modified状态。
3、Shared (共享)
- Shared状态表示有多个处理器都持有该缓存行的副本,但没有任何一个处理器对其进行了修改。在这种状态下,所有持有该缓存行副本的处理器都可以安全地读取数据,但不能写入。
- 如果任何一个处理器尝试写入一个处于Shared状态的缓存行,必须先使其他处理器持有的该缓存行副本失效,然后才能转换为Modified或Exclusive状态。
4、Invalid (无效)
- Invalid状态表示当前处理器不持有该缓存行的有效副本。当处理器需要访问一个无效的缓存行时,必须从主内存或其他处理器那里获取最新版本的数据。
(3)、协议运作流程
1、读取请求
当一个处理器尝试读取一个数据项时,它会检查自己的缓存中是否有该数据项的有效副本。如果有并且处于Shared或Exclusive状态,则直接使用;如果是Invalid状态,则需要从其他处理器或主内存中请求最新的数据副本。
2、写入请求
当一个处理器尝试写入一个数据项时,它首先需要确保自己是唯一拥有该数据项的处理器,并且该数据项处于Modified或Exclusive状态。如果数据项处于Shared状态,处理器必须发送消息给其他持有该数据项副本的处理器,要求它们将副本设置为Invalid,然后才能进行写操作。
(4)、MESI协议的优点
1、减少不必要的内存访问
通过缓存一致性协议,减少了处理器对主内存的频繁访问,提高了系统的整体性能。
2、简化了多处理器系统的设计
MESI协议提供了一种简单而有效的方法来管理多处理器环境中的缓存一致性问题。
(5)、示例说明
当CPU写数据时,如果发现操作的变量是共享变量,即在其它CPU中也存在该变量的副本,系统会发出信号通知其它CPU将该内存变量的缓存行设置为无效。如下图所示,CPU1和CPU3 中num=1已经失效了。
当其它CPU读取这个变量的时,发现自己缓存该变量的缓存行是无效的,那么它就会从内存中重新读取。
如下图所示,CPU1和CPU3发现缓存的num值失效了,就重新从内存读取,num值更新为2。
(6)、总线嗅探
那其他CPU是怎么知道要将缓存更新为失效的呢?这里是用到了总线嗅探技术。
每个CPU不断嗅探总线上传播的数据来检查自己缓存值是否过期了,如果处理器发现自己的缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效状态,当处理器对这个数据进行修改操作的时候,会重新从内存中把数据读取到处理器缓存中。
(7)、总线风暴
总线嗅探技术有哪些缺点?
由于MESI缓存一致性协议,需要不断对主线进行内存嗅探,大量的交互会导致总线带宽达到峰值。因此不要滥用volatile,可以用锁来替代,看场景啦~
(8)、窥探(snooping)协议
(1)、概述
“窥探(Snooping)”协议是一种常用的缓存一致性协议,主要用于多处理器系统中,确保各个处理器缓存的数据一致性。在窥探协议中,每个处理器都会监听总线上发生的事务,以确定是否需要更新其缓存中的数据。这种协议通过广播的方式让所有处理器都能及时了解其他处理器的缓存操作,从而保持数据的一致性。
(2)、工作原理
1、总线监听
- 每个处理器不仅处理自己的指令,还会监听总线上发生的其他处理器发出的请求。
- 这些请求包括读请求、写请求等。
2、响应机制 - 当一个处理器发出读请求时,其他处理器会检查自己的缓存,看看是否持有请求的数据项。
- 如果某个处理器持有该数据项的副本,并且该副本是最新的(例如处于Modified或Exclusive状态),则该处理器会响应读请求,并将数据直接发送给请求方。
- 如果请求的数据项在某个处理器的缓存中处于Invalid状态,或者请求的是写操作,处理器会根据具体情况进行相应的处理,比如使其他处理器持有的副本失效。
3、状态更新 - 当一个处理器收到其他处理器的写请求时,如果它持有该数据项的副本,会将副本标记为Invalid,以确保数据的一致性。
- 类似地,当一个处理器收到其他处理器的读请求时,如果它持有该数据项的副本,会根据请求的情况更新缓存行的状态。
(3)、示例说明
假设有一个多处理器系统,包含两个处理器 P1 和 P2,它们都使用窥探协议来维护缓存一致性。
1、初始状态
- P1 缓存中有一个数据项 A,状态为 Exclusive。
- P2 缓存中没有数据项 A。
2、P2 发出读请求
- P2 需要读取数据项 A,于是向总线发出读请求。
- P1 监听到这个读请求,发现自己的缓存中有数据项 A 的副本,状态为 Exclusive。
- P1 将数据项 A 发送给 P2,并将自己缓存中的数据项 A 状态更新为 Shared。
- P2 收到数据项 A,并将其存入自己的缓存中,状态为 Shared。
3、P1 发出写请求
- P1 需要写入数据项 A,于是向总线发出写请求。
- P2 监听到这个写请求,发现自己缓存中有数据项 A 的副本,状态为 Shared。
- P2 将自己缓存中的数据项 A 标记为 Invalid。
- P1 更新自己缓存中的数据项 A,状态变为 Modified。
(4)、优点
1、简单易实现:窥探协议通过简单的总线监听和响应机制,能够有效地维护缓存一致性。
2、低延迟:因为数据可以在处理器之间直接传递,不需要每次都访问主内存,所以可以降低数据访问的延迟。
(5)、缺点
1、总线带宽限制:随着处理器数量的增加,总线上的流量会增加,可能导致总线带宽不足,影响系统性能。
2、功耗较高:每个处理器都需要不断监听总线上的事务,增加了功耗。
总之,窥探协议是一种有效的缓存一致性解决方案,特别适用于处理器数量较少的多处理器系统。在大规模多处理器系统中,可能会采用更复杂的协议来提高性能和效率。
(9)、总结
“窥探(Snooping)”协议和MESI协议实际上是相辅相成的。MESI协议是一种具体的缓存一致性协议,它定义了缓存行的四种状态(Modified、Exclusive、Shared、Invalid),并规定了如何在这些状态之间转换。而窥探协议是一种实现缓存一致性的方法,通过监听总线上的事务来确保各个处理器缓存的数据一致性。
简单说就是MESI做了实现的定义,窥探(Snooping)”协议是具体实现的案例,保证了缓存数据一致性。
学海无涯苦作舟!!!
相关文章:
多线程篇-4--重点概念1(volatile,Synchronized,内存屏障,MESI协议)
一、volatile (1)、简述 volatile是java提供的一个关键字,英文意思为不稳定的。 可以保障被声明对象的可见性和一定程度上的有序性,但不能保证操作的原子性。 当一个变量被声明为volatile时,意味着该变量的值会直接从…...
本地学习axios源码-如何在本地打印axios里面的信息
1. 下载axios到本地 git clone https://github.com/axios/axios.git 2. 下载react项目, 用vite按照提示命令配置一下vite react ts项目 npm create vite my-vue-app --template react 3. 下载koa, 搭建一个axios请求地址的服务端 a.初始化package.json mkdir koa-server…...
1、SpringBoo中Mybatis多数据源动态切换
我们以一个实例来详细说明一下如何在SpringBoot中动态切换MyBatis的数据源。 一、需求 1、用户可以界面新增数据源相关信息,提交后,保存到数据库 2、保存后的数据源需要动态生效,并且可以由用户动态切换选择使用哪个数据源 3、数据库保存了多个数据源的相关记录后,要求…...
【浏览器】缓存与存储
我是目录 浏览器缓存为什么需要浏览器缓存?对浏览器的缓存机制的理解协商缓存和强缓存的区别强缓存协商缓存 点击刷新按钮或者按 F5、按 CtrlF5 (强制刷新)、地址栏回车有什么区别? 浏览器本地存储前端储存的方式有哪些࿱…...
积鼎科技携手西北工业大学动力与能源学院共建复杂多相流仿真联合实验室
11月26日,复杂多相流仿真联合实验室揭牌仪式及技术研讨活动在西北工业大学动力与能源学院成功举办。复杂多相流仿真联合实验室是由西北工业大学动力与能源学院牵头,携手上海积鼎信息科技有限公司与三航铸剑(西安)科技发展有限公司…...
5. langgraph实现高级RAG (Adaptive RAG)
1. 数据准备 from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.document_loaders import WebBaseLoader from langchain_community.vectorstores import Chromaurls ["https://lilianweng.github.io/posts/2023-06-23-age…...
Postman设置接口关联,实现参数化
🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 postman设置接口关联 在实际的接口测试中,后一个接口经常需要用到前一个接口返回的结果, 从而让后一个接口能正常执行,这…...
代码随想录day02--链表
移除链表元素 题目 地址:https://leetcode.cn/problems/remove-linked-list-elements/description/ 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。 思路是使用虚拟节点的…...
杰发科技AC7803——不同晶振频率时钟的配置
计算公式 PLL_POSDIV [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62] PLL_PREDIV_1 1 2 4 USE_XTAL 24M SYSCLK_FREQ 64M SYSCLK_DIVIDER 1 VCO USE_XTAL*…...
ArcGIS栅格影像裁剪工具
1、前言 在最近的栅格转矢量处理过程中,发现二值化栅格规模太大,3601*3601,并且其中的面元太过细碎,通过arcgis直接栅格转面有将近几十万的要素,拿这样的栅格数据直接运行代码,发现速度很慢还难以执行出来结…...
【查询目录】.NET开源 ORM 框架 SqlSugar 系列
.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列【数据事务…...
docker快速安装zookeeper
一、拉取镜像 docker pull zookeeper:3.9.3 二、启动zookeeper docker run --restartalways -d --name zookeeper -p 2181:2181 -v /etc/localtime:/etc/localtime zookeeper:3.9.3 如果需要挂载zookeeper文件及目录,则参数增加: -v /mydata/zookeeper/d…...
MySQL中如何减少回表
在MySQL中,回表是指在使用非聚集索引进行查询时,如果需要获取的数据不在索引页中,就需要根据索引页中的指针返回到数据表中查找实际数据行的过程。这个过程会增加额外的磁盘I/O操作,降低查询性能,特别是在查询大量数据…...
初始Python篇(7)—— 正则表达式
找往期文章包括但不限于本期文章中不懂的知识点: 个人主页:我要学编程(ಥ_ಥ)-CSDN博客 所属专栏: Python 目录 正则表达式的概念 正则表达式的组成 元字符 限定符 其他字符 正则表达式的使用 正则表达式的常见操作方法 match方法的…...
洛谷P1443 马的遍历
简单的bfs 题目链接 P1443 马的遍历 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述 有一个 nm 的棋盘,在某个点(x,y) 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。 输入格式 输入只有一行四个整数,分别为 n…...
代理IP地址的含义与设置指南
在数字化时代,互联网已经成为我们日常生活不可或缺的一部分。然而,在享受互联网带来的便利的同时,我们也面临着隐私泄露、访问限制等问题。代理IP地址作为一种有效的网络工具,能够帮助我们解决这些问题。本文将详细介绍代理IP地址…...
Vue--------导航守卫(全局,组件,路由独享)
全局导航守卫 beforeEach 全局前置守卫 afterEach 全局后置守卫 路由独享守卫 beforeEnter 路由独享守卫 组件导航守卫 beforeRouteEnter 进入组件前 beforeRouteUpdate 路由改变但是组件复调用 beforeRouteLeave 离开组件之前 执行顺…...
ElasticSearch7.x入门教程之全文搜索(七)
文章目录 前言一、多条件查询:bool query二、更加精准查询:dis_max query总结 前言 这里再接着上一篇文章继续记录。非常感谢江南一点雨松哥的文章。 欢迎大家去查看,地址:http://www.javaboy.org 一、多条件查询:boo…...
Adversarial Learning forSemi-Supervised Semantic Segmentation
首先来了解一下对抗学习: 对抗样本:将真实的样本添加扰动而合成的新样本,是由深度神经网络的输入的数据和人工精心设计好的噪声合成得到的,但它不会被人类视觉系统识别错误。然而在对抗数据面前,深度神经网络却是脆弱…...
UCOS-II 自学笔记
摘抄于大学期间记录在QQ空间的一篇自学笔记,当前清理空间,本来想直接删除掉的,但是感觉有些舍不得,因此先搬移过来。 一、UC/OS_II体系结构 二、UC/OS_II中的任务 1、任务的基本概念 在UCOS-II中,通常把一个大型任…...
C++ - 二叉搜索树讲解
二叉搜索树概念和定义 二叉搜索树是一个二叉树,其中每个节点的值都满足以下条件: 节点的左子树只包含小于当前节点值的节点。节点的右子树只包含大于当前节点值的节点。左右子树也必须是二叉搜索树。 二叉树搜索树性质 从上面的二叉搜索树定义中可以了…...
基于开源云原生数据仓库 ByConity 体验多种数据分析场景
基于开源云原生数据仓库 ByConity 体验多种数据分析场景 业务背景什么是 ByConity上手实测环境要求测试操作远程登录 ECS 服务器windows10 自带连接工具 执行查询 ByConity 相对于 ELT 能力的优化提升并行度任务级重试并行写入简化数据链路 业务背景 大家都知道,在…...
RabbitMQ 消息确认机制
RabbitMQ 消息确认机制 本文总结了RabbitMQ消息发送过程中的一些代码片段,详细分析了回调函数和发布确认机制的实现,以提高消息传递的可靠性。 返回回调机制的代码分析 主要用途 这个代码主要用于设置RabbitMQ消息发送过程中的回调函数,即…...
Node.js:开发和生产之间的区别
Node.js 中的开发和生产没有区别,即,你无需应用任何特定设置即可使 Node.js 在生产配置中工作。但是,npm 注册表中的一些库会识别使用 NODE_ENV 变量并将其默认为 development 设置。始终在设置了 NODE_ENVproduction 的情况下运行 Node.js。…...
【QT】背景,安装和介绍
TOC 目录 背景 GUI技术 QT的安装 使用流程 QT程序介绍 main.cpp编辑 Wiget.h Widget.cpp form file .pro文件 临时文件 C作为一门比较古老的语言,在人们的认知里始终是以底层,复杂和高性能著称,所以在很多高性能需求的场景之下…...
从0到1搭建webpack
好,上一篇文章我们说了一下在react中怎么弄这个webpack,那么现在在说一下不用react我们又该怎么配置,这些呢也都是我自己通弄过看视频自己总结的,拿来给大家分享一下。 前期准备条件 1、nvm(可以快速切换node版本&am…...
针对解决conda环境BUG的个人笔记
1-conda学习&安装 安装视频: 零基础教程:基于Anaconda和PyCharm配置Pytorch环境_哔哩哔哩_bilibili 安装过程: MX250笔记本安装Pytorch、CUDA和cuDNN-CSDN博客 Win10MX250CUDA10.1cuDNNPytorch1.4安装测试全过程(吐血)_nvidia geforc…...
读《Effective Java》笔记 - 条目13
条目13:谨慎重写clone方法 浅拷贝和深拷贝 浅拷贝(Shallow Copy) 浅拷贝 只复制对象本身,而不复制对象引用的成员。 对于引用类型的字段,浅拷贝会将原对象的引用复制到新对象中,而不会创建新对象实例。因…...
SQL 之连接查询
SQL 连接查询:深入理解 JOIN 操作 在数据库管理中,连接查询(JOIN)是一种基本而强大的操作,它允许我们从两个或多个表中检索数据。SQL 中的 JOIN 操作使得数据整合变得简单,这对于数据分析和报告至关重要。…...
vscode切换anaconda虚拟环境解释器不成功
问题: 切换解释器之后运行代码还是使用的原来的解释器 可以看到,我已经切换了“nlp”解释器,我的nltk包只在“nlp”环境下安装了,但是运行代码依然是"torch"解释器,所以找不到“nltk”包。 在网上找了各种…...
绍兴公司网站建设/软件外包企业排名
1.高斯消元概念 高斯消元法的本质是行变换,是化系数矩阵A为上三角矩阵。当矩阵A的秩小于未知元个数时,就存在基础解系。 2. 基本性质 2.1 是否要求系数矩阵A必须为方阵? 对于齐次线性方程组,只要考虑系数矩阵A。如果矩阵A是方阵…...
网站建设注意哪些方面/怎么做网站?
题意: 给出T条有权值的边,求从节点1到节点n的最短路径。 思路: 用了迪杰斯特拉算法(dijkstra),模板题。注意:输入的边有可能是重边的,就是重复输入同一个边,但是权值不…...
网站页面上的悬浮窗怎么做/关键词林俊杰百度云
昨天周日去清华参加人民搜索笔试,人好多啊,据说简历收到了两千份,也不知道他们招多少人,估计签百度的人为了户口都杀回来了,估计竞争会很激烈。 考题的形式是前面是多不定项选择20个,后面是5个算法题&#…...
程序外包价格/杭州seo排名优化外包
sem_init: int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value)); 功能:初始化信号量 返回值:创建成功返回0,失败返回-1 参数sem:指向信号量结构的一个指针 参数pshared:不为0时此…...
营销型网站建设需要懂什么/新的营销方式有哪些
ERP-ORACLE-EBS-AP预付款管理--操作AP预付款管理--操作预付款发票录入:预付款的录入和标准发票一样,只是在类型时选择“预付款”;对于预付款类型的发票,实际支付了多少预付款就录入多少,因为预付款必须要全额支付才能核…...
凡客网站网址/国际大新闻最新消息
php获取url中的参数信息,是PHP面试过程中常见的考点之一,也是我们日常项目开发过程中,经常需要实现的一个功能,我们可以通过PHP中parse_url等相关函数来实现。那么对于PHP新手来说,具体怎么实现可能有一定的难度&#…...