【Java集合篇】ConcurrentHashMap是如何保证线程安全的

在这里插入图片描述

ConcurrentHashMap是如何保证线程安全的

  • ✔️典型解析
  • ✔️ 拓展知识仓
    • ✔️ 什么是CAS(Compare And Swap)
      • ✔️CAS和互斥量有什么区别
      • ✔️如何使用CAS和互斥量
    • ✔️CAS和Synchronized的区别
    • ✔️ConcurrentHashMap的优缺点
    • ✔️能用ConcurrentHashMap实现队列吗??
  • ✔️终极环节(源码解析)


✔️典型解析


在JDK 1.7中,ConcurrentHashMap使用了分段锁技术,即将哈希表分成多个段,每个段拥有一个独立的锁。这样可以在多个线程同时访问哈希表时,只需要锁住需要操作的那个段,而不是整个哈希表,从而提高了并发性能。


虽然JDK 1.7的这种方式可以减少锁竞争,但是在高并发场景下,仍然会出现锁亮争,从而导致性能下降。


在JDK 1.8中,ConcurrentHashMap的实现方式进行了改进,使用分段锁 (思想)和“CAS+Synchronized”的机制来保证线程安全。在JDK 1.8中,ConcurrentHashMap会在添加元素时,如果某个段为空,那么使用CAS操作来添加新节点;如果段不为空,使用Synchronized锁住当前段,再次尝试put。这样可以避免分段锁机制下的锁粒度太大,以及在高并发场景下,由于线程数量过多导致的锁竞争问题,提高了并发性能。


分段锁其实是一种思想。1.7中的”分段锁”和1.8中的”分段锁"不是一回事儿,而当我们说1.7的分段锁,一般特指是他的Segment,而1.8中我们指的是一种分段加锁的思想,加锁的时候只对当前段加锁,而不是整个map加锁。


ConcurrentHashMap 是 Java 并发包(java.util.concurrent)中的一个类,它为多线程环境中的高并发访问提供了高效的解决方案。ConcurrentHashMap 通过分段锁机制和动态调整等技术,在保证线程安全的同时,也提供了很高的并发性能。


以下是 ConcurrentHashMap 保证线程安全的几个关键点


  1. 分段锁机制:ConcurrentHashMap 使用了分段锁的技术,将数据分成多个段(Segment),每个段都是一个小的哈希表。当一个线程访问某个段的时候,只会锁定这个段,不会阻塞其他段的访问。这样,多个线程可以同时访问不同的段,从而实现高并发。

  1. 锁分离:除了传统的互斥锁,ConcurrentHashMap 还使用了分离锁(Striped Locking)。这是一种锁的优化技术,允许多个线程同时获取同一把锁的不同部分,提高了并发性能。

  1. 动态调整:ConcurrentHashMap 在运行过程中会根据实际负载情况动态调整段的大小。当某个段负载过高时,会分裂出新的段;当某个段负载过低时,会合并相邻的段。这种动态调整可以保证在各种负载情况下,都能提供高效的并发性能。

  1. CAS操作:ConcurrentHashMap 使用 CAS(Compare-and-Swap)操作来保证线程安全。CAS 是一种无锁的原子操作,可以在不阻塞线程的情况下,安全地更新内存中的变量。

  1. 红黑树:为了解决哈希冲突,ConcurrentHashMap 在链表长度超过一定阈值时,会将链表转换为红黑树。红黑树是一种自平衡的二叉查找树,可以在 O(log n) 的时间复杂度下完成查找、插入和删除操作。

这些机制和技术共同作用,使得ConcurrentHashMap在多线程环境下提供了高效的并发访问,同时也保证了线程安全。


看一个示例

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;/**
*   @author xinbaobaba
*   演示ConcurrentHashMap在多线程环境中的线程安全性和高性能。
*   将使用多个线程同时对ConcurrentHashMap进行读和写操作,并观察其表现
*/
public class ConcurrentHashMapComplexExample {public static void main(String[] args) {// 创建一个ConcurrentHashMap对象ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();// 创建一个线程池ExecutorService executor = Executors.newFixedThreadPool(5);// 启动多个线程同时进行读和写操作for (int i = 0; i < 10; i++) {executor.execute(() -> {try {// 随机等待一段时间,模拟异步操作Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 随机选择一个键进行读写操作String key = "key" + (int) (Math.random() * 10);if (Math.random() > 0.5) {// 有50%的概率进行写操作,即添加一个键值对map.put(key, "value" + key);} else {// 50%的概率进行读操作,即获取一个键的值String value = map.get(key);System.out.println("Read value for key " + key + ": " + value);}});}// 关闭线程池,等待所有线程执行完毕executor.shutdown();try {if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {executor.shutdownNow(); // 强制关闭线程池}} catch (InterruptedException e) {executor.shutdownNow(); // 强制关闭线程池}}
}

示例中,创建了一个包含5个线程的线程池。每个线程随机等待一段时间后,执行一个读或写操作。读操作使用map.get方法获取键的值,而写操作使用 map.put 方法添加一个键值对。由于ConcurrentHashMap是线程安全的,因此这些读和写操作可以并发执行,而不会互相干扰。最后,我们等待所有线程执行完毕,并关闭线程池。


趁热打铁,我们来看一个实际应用中的Demo:


import java.util.concurrent.ConcurrentHashMap;/**
*  Demo之如何使用ConcurrentHashMap实现一个线程安全的银行账户转账系统
*/
public class BankAccountTransferSystem {private final ConcurrentHashMap<String, Integer> accounts;public BankAccountTransferSystem() {accounts = new ConcurrentHashMap<>();// 初始化账户余额accounts.put("Alice", 1000);accounts.put("Bob", 2000);}public void transfer(String from, String to, int amount) {int fromBalance = accounts.get(from);if (fromBalance >= amount) {int toBalance = accounts.get(to);accounts.put(from, fromBalance - amount);accounts.put(to, toBalance + amount);System.out.println("Transfer successful: " + amount + " from " + from + " to " + to);} else {System.out.println("Insufficient funds: " + amount + " from " + from);}}
}

Demo中,创建了一个名为BankAccountTransferSystem的类,它使用ConcurrentHashMap来存储账户信息,包括账户名和账户余额。类中的transfer方法用于执行转账操作,它接受三个参数:转出账户名、转入账户名和转账金额。在执行转账操作之前,我们首先获取转出账户的当前余额,并检查是否足够支付转账金额。如果余额足够,则从转出账户中扣除转账金额,并将相同金额添加到转入账户中。最后,我们打印出转账是否成功的消息。由于ConcurrentHashMap是线程安全的,因此多个线程可以同时调用transfer方法来执行转账操作,而不会出现数据不一致的问题。这个例子可以应用于在线银行系统、电子支付平台等需要高并发、高可靠性的金融应用场景。


✔️ 拓展知识仓


✔️ 什么是CAS(Compare And Swap)


CAS是Compare And Swap的缩写,直译为“比较并交换”。CAS是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。CAS是一种原子操作,用于实现多线程同步的原子指令。在多线程并发中,CAS能够保证共享资源的原子性操作。具体来说,它比较一个内存位置的值,并且只有当该值与预期值相等时,才会修改这个内存位置的值为新值。CAS返回操作是否成功,如果内存位置原来的值用于判断是否CAS成功。


在Java并发应用中,CAS通常用于实现无锁的解决方案,这是一种基于乐观锁的操作。相对于synchronized或Lock等锁机制,CAS是一种轻量级的实现方案。当多个线程同时对某个资源进行CAS操作时,只有一个线程能够操作成功,而其他线程则只收到操作失败的信号。CAS不会阻塞其他线程,而是在操作失败时重新读取数据并进行比较。


在并发编程中,CAS的优势在于它可以避免使用重量级锁所带来的性能开销,并且能够在多线程并发的情况下保证共享资源的原子性操作。然而,CAS也有一些缺点,例如循环时间长开销大、只能保证一个共享变量的原子操作以及对多个共享变量操作时不能保证原子性等。


CAS是一种轻量级锁机制,用于实现多线程同步的原子指令,它比较并交换内存中的共享数据,保证了新的值总是基于最新的信息计算。在并发编程中,CAS可以避免使用重量级锁所带来的性能开销,并且能够在多线程并发的情况下保证共享资源的原子性操作。


✔️CAS和互斥量有什么区别


CAS(Compare-and-Swap)互斥量(Mutex)都是多线程编程中常用的同步原语,但它们的工作机制和适用场景有所不同。


  1. 工作机制:

  • CAS 是一种原子操作,用于实现无锁的同步。它比较并交换内存中的共享数据,只有当该值与预期值相等时,才会修改这个内存位置的值为新值。CAS 操作可以保证共享资源的原子性操作。
  • 互斥量则是一种锁机制,用于保护共享资源的访问,确保同一时刻只有一个线程可以访问被保护的代码或数据。互斥量通过阻塞线程的方式来避免同时被多个线程访问。

  1. 适用场景:

  • CAS 适用于实现无锁的同步,适用于读多写少的场景,可以避免线程阻塞和唤醒的开销。
  • 互斥量适用于需要保护共享资源的访问的场景,可以防止数据竞争和线程间的冲突。
  1. 性能:

  • CAS 通常比互斥量具有更好的性能,因为它避免了线程阻塞和唤醒的开销。在读多写少的场景下,使用 CAS 可以减少线程间的竞争和上下文切换的开销。
  • 互斥量可能会引起线程阻塞,导致性能下降。但是,互斥量可以保证共享资源的完整性和一致性。

CAS 和互斥量都是重要的同步原语,它们的选择取决于具体的场景和需求。在读多写少、对性能要求较高的场景下,可以考虑使用 CAS;在需要保护共享资源、保证一致性和完整性的场景下,可以使用互斥量。


✔️如何使用CAS和互斥量


一个简单的Java程序示例,说明如何使用CAS和互斥量:


import java.util.concurrent.atomic.AtomicInteger;public class CASExample {private AtomicInteger value = new AtomicInteger(0);public void increment() {// 使用CAS实现原子性增1操作int expectedValue = value.get();int newValue = expectedValue + 1;while (!value.compareAndSet(expectedValue, newValue)) {expectedValue = value.get();newValue = expectedValue + 1;}}public static void main(String[] args) {CASExample example = new CASExample();// 创建多个线程并发地调用increment方法for (int i = 0; i < 10; i++) {new Thread(() -> {for (int j = 0; j < 1000; j++) {example.increment();}}).start();}}
}

使用AtomicInteger来作为共享变量,通过CAS(Compare-and-Swap)操作来实现原子性增1操作。compareAndSet方法会比较当前值和预期值是否相等,如果相等则将值更新为新值,并返回true表示操作成功;否则,返回false表示操作失败。在失败时,我们再次获取当前值并尝试更新,直到成功为止。这样可以保证多个线程并发地调用increment方法时,共享变量的值能够正确地增加。


下面是一个使用互斥量(Mutex)的示例:


import java.util.concurrent.locks.Mutex;
import java.util.concurrent.locks.ReentrantMutex;public class MutexExample {private final Mutex mutex = new ReentrantMutex();private int sharedResource = 0;public void accessResource() {mutex.lock();  // 获取互斥量锁try {// 访问或修改共享资源sharedResource++;System.out.println("Accessed shared resource: " + sharedResource);} finally {mutex.unlock();  // 释放互斥量锁}}public static void main(String[] args) {MutexExample example = new MutexExample();// 创建多个线程并发地调用accessResource方法for (int i = 0; i < 10; i++) {new Thread(() -> {for (int j = 0; j < 1000; j++) {example.accessResource();}}).start();}}
}

使用ReentrantMutex作为互斥量。在访问共享资源之前,先通过lock()方法获取互斥量锁,确保同一时刻只有一个线程可以访问共享资源。在访问完成后,通过unlock()方法释放互斥量锁。这种方式可以避免多个线程同时访问共享资源导致的冲突和数据不一致问题。


✔️CAS和Synchronized的区别


CAS(Compare-and-Swap)Synchronized 是Java中两种处理线程同步的重要机制,它们在使用场景、实现原理和优缺点上存在显著的区别。


  1. 使用场景:CAS常用于实现轻量级的锁,适用于并发较低的情况。Synchronized则常用于高并发的情况,它会阻塞没有获得锁的线程,等待持有锁的线程释放锁。

  1. 实现原理:CAS是一种乐观锁机制,在操作一个值时,会先比较预期值和内存值是否相等,如果相等则更新内存值,否则就继续循环比较和交换。而Synchronized的实现机制更复杂,它会锁定被访问对象,防止其他线程访问。

  1. 原子性:CAS保证的是单个变量的原子性操作,而无法保证整个代码块的原子性。相比之下,Synchronized可以保证整个代码块的原子性。

  1. CPU开销:由于CAS机制不需要阻塞线程,所以其CPU开销较小,适用于并发较低的情况。而Synchronized在并发较高的情况下表现更佳,但其CPU开销较大,因为需要阻塞等待锁的线程。

总的来说,CAS和Synchronized都是用于处理线程同步的重要机制,各有其使用场景、实现原理、原子性以及CPU开销方面的特点。在实际应用中,需要根据具体需求选择使用哪种机制。


下面我们使用Demo来帮助理解:


  1. CAS示例:
import java.util.concurrent.atomic.AtomicInteger;/**
*    使用Java代码对CAS和Synchronized进行讲解的
*/
public class CASExample {private AtomicInteger value = new AtomicInteger(0);public void increment() {int expectedValue = value.get();int newValue = expectedValue + 1;while (!value.compareAndSet(expectedValue, newValue)) {expectedValue = value.get();newValue = expectedValue + 1;}}public static void main(String[] args) throws InterruptedException {CASExample example = new CASExample();Thread t1 = new Thread(() -> { example.increment(); });Thread t2 = new Thread(() -> { example.increment(); });t1.start();t2.start();t1.join();t2.join();System.out.println("Final Value: " + example.value); // 输出:Final Value: 2}
}

在这个示例中,使用了AtomicInteger类,它提供了CAS操作。increment方法通过循环使用compareAndSet方法来实现原子性增1操作。如果预期值与内存值相等,则将内存值更新为新值;否则,继续循环获取预期值并尝试更新。由于使用了CAS操作,两个线程可以并发地调用increment方法,最终共享变量的值为2。


  1. Synchronized示例:

public class SynchronizedExample {private int sharedResource = 0;public void accessResource() throws InterruptedException {synchronized (this) { // 获取当前对象的锁sharedResource++;System.out.println("Accessed shared resource: " + sharedResource);Thread.sleep(100); // 模拟耗时操作} // 释放锁}public static void main(String[] args) throws InterruptedException {SynchronizedExample example = new SynchronizedExample();Thread t1 = new Thread(() -> { example.accessResource(); });Thread t2 = new Thread(() -> { example.accessResource(); });t1.start();t2.start();t1.join();t2.join();System.out.println("Final Value: " + example.sharedResource); // 输出:Final Value: 2}
}

在这个示例中,我们使用了synchronized关键字来实现线程同步。在accessResource方法中,通过synchronized (this)获取当前对象的锁。这样,在同一时刻只有一个线程可以访问共享资源。由于使用了同步机制,两个线程会交替执行,最终共享变量的值为2。


✔️ConcurrentHashMap的优缺点


ConcurrentHashMap是Java中一个线程安全的哈希表实现,它支持高并发的读写操作。以下是ConcurrentHashMap的优缺点:


优点:


  1. 线程安全:ConcurrentHashMap提供了线程安全的操作,可以在多线程环境下使用,而不需要额外的同步措施。

  2. 高并发性能:通过分段锁机制,ConcurrentHashMap可以将整个哈希表分成多个段,每个段由独立的线程进行操作,从而大大提高了并发性能。

  3. 高效性能:ConcurrentHashMap在读写操作上具有较高的性能,尤其是在读操作方面,因为它不需要加锁就可以访问数据。

缺点:


  1. 无法保证顺序:由于ConcurrentHashMap采用哈希表结构,其迭代顺序并不固定,因此在某些情况下无法保证结果的顺序。

  2. 不适合作为队列使用:ConcurrentHashMap不适合用作队列或栈等需要先进先出(FIFO)或后进先出(LIFO)的数据结构,因为其迭代顺序并不符合这些数据结构的特性。

  3. 无法进行精确控制:由于ConcurrentHashMap采用分段锁机制,无法对整个哈希表进行精确控制,因此在某些情况下可能会存在数据不一致的问题。

ConcurrentHashMap适用于需要高并发读写的场景,尤其适合读操作较多的场景。如果需要保证结果的顺序或者作为队列使用,或者需要精确控制整个数据结构,那么应该考虑其他同步措施或使用其他数据结构。


✔️能用ConcurrentHashMap实现队列吗??


尽管ConcurrentHashMap适用于高并发的读写操作,但它不适合用作队列。队列是一种先进先出(FIFO)的数据结构,要求数据的顺序是按照入队顺序进行的。而ConcurrentHashMap的迭代顺序并不固定,因此无法保证数据的顺序。

如果你需要实现一个线程安全的队列,可以考虑使用Java提供的java.util.concurrent.BlockingQueue接口。BlockingQueue接口提供了线程安全的队列操作,包括入队、出队、等待和中断等操作,非常适合实现多线程之间的通信和协作。

下面是使用BlockingQueue实现队列的示例代码:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;public class ConcurrentQueue<T> {private final BlockingQueue<T> queue = new LinkedBlockingQueue<>();public void enqueue(T item) throws InterruptedException {queue.put(item); // 入队操作,如果队列已满则阻塞等待}public T dequeue() throws InterruptedException {return queue.take(); // 出队操作,如果队列为空则阻塞等待}
}

Demo中,使用LinkedBlockingQueue作为队列的实现,它是一个基于链接节点的、可伸缩的阻塞队列。enqueue方法使用put方法将元素加入队列,如果队列已满则阻塞等待;dequeue方法使用take方法从队列中取出元素,如果队列为空则阻塞等待。通过这种方式,可以实现线程安全的队列操作。


✔️终极环节(源码解析)


ConcurrentHashMap将哈希表分成多个段,每个段拥有一个独立的锁,这样可以在多个线程同时访问哈希表时,只需要锁住需要操作的那个段,而不是整个哈希表,从而提高了并发性能。下面是ConcurrentHashMap中分段锁的代码实现:


static final class Node<K,V> implements Map.Entry<K,V>  {final int hash;final K key;volatile V val;volatile Node<K,V> next;Node(int hash, K key, V val, Node<K,V> next) {this .hash = hash;this .key = key;this.val = val;this .next = next;}//..........}static final class Segment<K,V> extends Reentrantlock implements Serializable {private static final long serialVersionUID = 2249069246763182397L;transient volatile HashEntry<K,V>[] table;transient int count;transient int modCount;transient int threshold;final float loadFactor;
}

在上面的代码中,我们可以看到,每个 Segment 都是ReentrantLock的实现,每个Segment包含一个HashEntry数组,每个HashEntry则包合一个key-value键值对


接下来再看下在JDK 1.8中,下面是ConcurrentHashMap中CAS+Synchronized机制的代码实现:


public V put(K key, V value)  {if (value == null)throw new NullPointerException();//对 key 的 hashCode 进行扰动int hash = spread(key.hashCode());int binCount = 0;// xun环操作for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;//如果 table 为 null 或长度为 0,则进行初始化if (tab == null || (n = tab.length) == 0)tab = initTable();// 如果哈希槽为空,则通过 CAS 操作尝试插入新节点else if ((f = tabAt(tab, i = (n - 1) & hash)) == null)  {if (casTabAt(tab,i, null,new Node<K,V>(hash , key, value, null)))break;} //如果哈希槽处已经有节点,且 hash 值为 MOVED,则说明正在进行扩容,需要帮助迁移数据else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);// 如果哈希槽处已经有节点,且 hash 值不为 MOVED,则进行链表/红黑树的节点遍历或插入操作else {V oldVal = null;// 加锁,确保只有一个线程操作该节点的链表/红黑树synchronized (f) {if (tabAt(tab, i) == f) {// 遍历链表,找到相同 key 的节点,更新值或插入新节点binCount = 1;for (Node<k,V> e = f;; ++binCount) {K ek;if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek))))  {oldVal = e.val;if (!onlyIfAbsent)e.val = value;break;}Node<K ,V> pred = e;if ((e = e.next) == null) {// 将新节点插入到链表末尾if (casNext(pred, new Node<K,V>(hash, key.value, null)))  {break;}}}}// 历红黑树,找到相同 key 的节点,更新值或插入新节点else if (f instanceof TreeBin) {Node<k,V> p;binCount = 2;if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value:}}}}//如果插入或更新成功,则进行可能的红黑树化操作if (binCount != 0) {if (binCount >= TREEIFY_THRESHOLD) {treeifyBin(tab,i);}//如果替换旧值成功,则返回旧值if (oldVal != null)return oldVal;break;}}
}

在上述代码中,如果某个段为空,那么使用CAS操作来添加新节点: 如果某个段中的第一个节点的hash值为MOVED,表示当前段正在进行扩容操作,那么就调用helpTransfer方法来协助扩容:否则,使用Synchronized锁住当前节点,然后进行节点的添加操作

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

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

相关文章

python对常见的激活函数绘图操作(详细代码讲解)

写论文的时候需要做一些激活函数的图像&#xff0c;为此将常见的激活函数进行整理汇总了一下&#xff0c;方便后续的复习 激活函数的作用是为让模型处理非线性问题&#xff0c;故次激活函数都是非线性的 生活中&#xff0c;非线性问题占大多数&#xff0c;而模型的训练通常都是…

哈希表-散列表数据结构

1、什么是哈希表&#xff1f; 哈希表也叫散列表&#xff0c;哈希表是根据关键码值(key value)来直接访问的一种数据结构&#xff0c;也就是将关键码值(key value)通过一种映射关系映射到表中的一个位置来加快查找的速度&#xff0c;这种映射关系称之为哈希函数或者散列函数&…

Rollup-plugin-bundle-analyzer VS Rollup-plugin-visualizer

分析和可视化Rollup打包后的文件的插件 Rollup-plugin-bundle-analyzerRollup-plugin-visualizer Rollup-plugin-bundle-analyzer和Rollup-plugin-visualizer都是用于分析和可视化Rollup打包后的文件的插件&#xff0c;但它们在功能和使用方式上存在一些差异。 Rollup-plugi…

PostGIS教程学习十九:基于索引的聚簇

PostGIS教程学习十九&#xff1a;基于索引的聚簇 数据库只能以从磁盘获取信息的速度检索信息。小型数据库将完全位于于RAM缓存&#xff08;内存&#xff09;&#xff0c;并摆脱物理磁盘访问速度慢的限制。但是对于大型数据库&#xff0c;对物理磁盘的访问将限制数据库的信息检…

FFmpeg获取音视频流信息

文章目录 前言一、需求二、源码三、运行结果 前言 本文记录用 FFmpeg 获取视频流音频流的信息&#xff08;编码格式、分辨率、帧率、播放时长…&#xff09;&#xff0c;所用的工程基于上个博客编译成功的工程&#xff1a;使用FFmpeg4.3.1的SDK官方开发包编译ffmpeg.c 一、需求…

透明OLED拼接屏:重塑大屏显示的新篇章

随着科技的快速发展&#xff0c;大屏显示技术已经逐渐渗透到我们生活的方方面面。作为显示技术领域的一大革新&#xff0c;透明OLED拼接屏以其独特的透明显示特性&#xff0c;正逐渐成为大屏显示市场的新宠。尼伽小编将深入探讨透明OLED拼接屏的技术特点、应用场景以及市场前景…

中国葡萄酒消费者的口味偏好

有一段时间&#xff0c;“中国口味”的问题是全世界葡萄酒销售者的热门话题&#xff0c;因为他们积极探索每一个线索&#xff0c;以发现让他们在市场上领先的秘密。为此进行了大量研究&#xff0c;多年前葡萄酒销售商或多或少形成了一个共识&#xff1a;尽管中国人的口味差异很…

系列十四、while do...while switch模板代码

一、while & do...while & switch模板代码 1.1、while /*** 需求&#xff1a;使用while循环打印5遍Hello World!*/ Test public void print5() {int i 1;while (i < 5) {System.out.println("Hello World! " LocalDateTime.now());// 线程休眠&#x…

Spring MVC学习之——了解MVC设计模式

MVC设计模式 MVC介绍 MVC 模式代表 Model-View-Controller&#xff08;模型-视图-控制器&#xff09; 模式。这种模式用于应用程序的分层开发。 Model&#xff08;模型&#xff09; - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑&#xff0c;在数据变化时更新…

Java项目:112SSM在线电影订票系统

博主主页&#xff1a;Java旅途 简介&#xff1a;分享计算机知识、学习路线、系统源码及教程 文末获取源码 一、项目介绍 在线电影订票系统基于SpringSpringMVCMybatis开发&#xff0c;系统分为前台和后台&#xff0c;前台主要用来用户浏览电影信息&#xff0c;订票&#xff0c…

人工智能任务2-读懂Transformer模型的十个灵魂拷问问题,深度理解Transformer模型架构

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能任务2-读懂Transformer模型的十个灵魂拷问问题&#xff0c;深度理解Transformer模型架构。Transformer模型是一种基于自注意力机制的神经网络架构&#xff0c;被广泛用于自然语言处理任务中&#xff0c;如机…

系分笔记数据库技术之数据库安全措施

文章目录 1、概要2、数据库的保护措施3、数据库的故障4、备份和日志5、总结 1、概要 数据库设计是考试重点&#xff0c;常考和必考内容&#xff0c;本篇主要记录了知识点&#xff1a;数据库故障及解决、数据库安全保护措施和数据库备份及恢复。 2、数据库的保护措施 数据库安全…

云卷云舒:【实战篇】云主机/虚拟机迁移

1. 简介 用户原有业务通过不同版本型号、不同操作系统的主机承载&#xff0c;形式上包括物理服务器、虚拟机、公有云主机等。随着业务不断扩张&#xff0c;需要将其业务云化转型&#xff0c;必须保证上云过程数据完整&#xff0c;业务平滑过度。 如果将所有业务系统都重新部署…

以太坊开发者会议回顾:坎昆升级、硬分叉与布拉格

作者&#xff1a;Christine Kim Galaxy研究副总裁 编译&#xff1a;秦晋 碳链价值 2024年1月4日&#xff0c;以太坊开发人员齐聚Zoom for All Core Developers Execution (ACDE) Call #178 上。ACDE电话会议通常由以太坊基金会协议负责人Tim Beiko主持&#xff0c;是一个开发人…

Orchestrator源码解读2-故障失败发现

目录 目录 前言 核心流程函数调用路径 GetReplicationAnalysis 故障类型和对应的处理函数 拓扑结构警告类型 与MHA相比 前言 Orchestrator另外一个重要的功能是监控集群&#xff0c;发现故障。根据从复制拓扑本身获得的信息&#xff0c;它可以识别各种故障场景。Orchest…

Apollo基础 - Frenet坐标系

Frenet与笛卡尔坐标系的转换详细推导见&#xff1a;b站老王 自动驾驶决策规划学习记录&#xff08;四&#xff09; Apollo相关代码&#xff1a; modules/common/math/cartesian_frenet_conversion.h #pragma once #include <array> #include "modules/common/mat…

怎么一边讲PPT一边录视频 如何一边录制PPT一边录制人像 录屏软件免费录屏 PPT录制怎么录制

随着新媒体技术的发展&#xff0c;短视频和直播越来越火。越来越多的小伙伴加入了视频制作的大军&#xff0c;那么你想知道怎么一边讲PPT一边录视频&#xff0c;如何一边录制PPT一边录制人像吗&#xff1f; 一、怎么一边讲PPT一边录视频 我们可以借助PPT本身自带的屏幕录制功能…

Linux的发展历程:从诞生到全球应用

一、前言 Linux作为一个开源操作系统&#xff0c;经历了令人瞩目的发展历程。从最初的创意到如今在全球范围内得到广泛应用&#xff0c;Linux不仅是技术的杰出代表&#xff0c;更是开源精神的典范。本文将追溯Linux的发展历程&#xff0c;深入了解它是如何从一个个人项目演变为…

【docker笔记】Docker容器数据卷

Docker容器数据卷 卷就是目录或者文件&#xff0c;存在于一个或多个容器中&#xff0c;由docker挂载到容器&#xff0c;但不属于联合文件系统&#xff0c;因此能够绕过Union File System提供一些用于持续存储或共享数据的特性 卷的设计目的就是数据的持久化&#xff0c;完全独…

VMware Workstation安装以及配置模板机

文章目录 一、VMware Workstation软件下载安装1、下载2、安装 二、CentOS7模板机安装1、创建虚拟机2、安装系统 三、网络配置 一、VMware Workstation软件下载安装 1、下载 下载地址&#xff1a;https://download3.vmware.com/software/wkst/file/VMware-workstation-full-15…