Java并发 | 常见线程安全容器

news2024/11/25 8:18:36

文章目录

  • 简介
  • 一、Hash表
    • 🚣1、ConcurrentHashMap
      • 1.1 内部实现原理
      • 1.2 并发操作方法
      • 1.3 ConcurrentHashMap与Hashtable的比较
  • 二、集合
    • 🚣2、CopyOnWriteArrayList
      • 2.1 内部实现原理
      • 2.2 Copy-On-Write(COW)设计思想
      • 2.3 实操
  • 三、Map
    • 🚣3、ConcurrentSkipListMap
      • 3.1 跳表(Skip List)
      • 3.2 并发操作方法
  • 四、队列
    • 🚣4、ConcurrentLinkedQueue
      • 4.1 内部实现原理
      • 4.2 并发操作方法
    • 🚣5、BlockingQueue(阻塞队列)
      • 5.1 内部实现原理
      • 5.2 阻塞队列和非阻塞队列
      • 5.3 使用场景和示例
    • 🚣6、ConcurrentLinkedDeque(双端队列)
      • 6.1 内部实现原理
      • 6.2 并发操作方法



简介

  线程安全数据类型通常提供了一些同步机制来保证数据的一致性。这些机制可以包括锁、互斥量、原子操作、无锁算法等。会在多个线程同时访问数据时进行同步操作,以保证每个操作的原子性和正确性。

一、Hash表

🚣1、ConcurrentHashMap


  我们知道HashMap是非线程安全的,在并发环境下容易导致数据错误。ConcurrentHashMap是Java1.5版本推出的线程安全容器,它适用于以下场景点:

  • 高并发读写:当多个线程需要同时读写哈希表时,ConcurrentHashMap能够提供更好的并发性能。
  • 大规模数据:当哈希表中的数据量较大时,ConcurrentHashMap的分段锁机制能够减小锁的粒度,提高并发更新的效率。

1.1 内部实现原理

  ConcurrentHashMap的内部实现原理主要包括以下几点:

  • 分段锁机制(JDK1.6):ConcurrentHashMap将整个哈希表分成多个段(Segment),每个段维护着一个独立的锁。不同线程对不同段的操作可以并发进行,从而提高了并发性能。
  • CAS(JDK1.8):在1.8的时候摒弃了segment臃肿的设计,直接针对的是Node[] tale数组中的每一个桶,进一步减小了锁粒度,插入时使用CAS自旋锁的方式将值插入。
  • 数组+链表/红黑树:每个段内部使用数组+链表(或红黑树)的数据结构来存储键值对。当链表长度超过阈值时,链表会转换为红黑树,以提高查找的效率。
  • CAS操作:ConcurrentHashMap使用CAS乐观锁(Compare and Swap)操作来实现并发更新,避免了使用传统的锁机制带来的性能开销。

插入数据流程:

1.做插入操作时,首先进入乐观锁(CAS)
2.然后,在乐观锁中判断容器是否初始化, 如果没初始化则初始化容器
3.如果已经初始化,则判断该hash位置的节点是否为空,如果为空,则通过CAS操作进行插入
4.如果该节点不为空,再判断容器是否在扩容中,如果在扩容,则帮助其扩容。
5.如果没有扩容,则进行最后一步,先加锁,然后找到hash值相同的那个节点(hash冲突)
6.循环判断这个节点上的链表,决定做覆盖操作还是插入操作。
7.循环结束,插入完毕。

1.2 并发操作方法

  ConcurrentHashMap提供了一系列的并发操作方法,常用的包括:

  • put(key, value):向ConcurrentHashMap中插入键值对。
  • get(key):根据键获取对应的值。
  • remove(key):根据键移除对应的键值对。
  • size():返回ConcurrentHashMap中键值对的数量。
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        // 创建ConcurrentHashMap实例
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

        // 并发插入键值对
        map.put("key1", 1);
        map.put("key2", 2);
        map.put("key3", 3);

        // 并发读取键值对
        int value1 = map.get("key1");
        int value2 = map.get("key2");
        int value3 = map.get("key3");

        // 并发移除键值对
        map.remove("key1");

        // 获取键值对数量
        int size = map.size();

        System.out.println("value1: " + value1);
        System.out.println("value2: " + value2);
        System.out.println("value3: " + value3);
        System.out.println("size: " + size);
    }
}

1.3 ConcurrentHashMap与Hashtable的比较

  同ConcurrentHashMap一样的线程安全容器还有Hashtable,ConcurrentHashMap从JDK 1.5开始引入,而Hashtable则从JDK 1.0就已经存在。

特性ConcurrentHashMapHashtable
线程安全性高并发环境下提供线程安全操作高并发环境下提供线程安全操作(通过synchronized关键字实现)
锁粒度分段锁(Segment)整个哈希表的锁
性能在高并发环境下具有更好的性能和可伸缩性在高并发环境下性能较差,因为整个哈希表被单个锁保护,可能导致竞争瓶颈
迭代器弱一致性ConcurrentHashMap的迭代器提供弱一致性(不一定能反映最新的修改)Hashtable的迭代器是强一致性的(反映最新的修改)
允许null键和null值允许不允许
继承关系实现了ConcurrentMap接口,继承自AbstractMap类继承自Dictionary类,不推荐在新代码中使用

  多线程环境下,推荐使用ConcurrentHashMap而不是HashTable。



二、集合

🚣2、CopyOnWriteArrayList


  ArrayList也不是线程安全的数据类型,想要在并发环境下使用List类型的变量,可以使用CopyOnWriteArrayList,它适用于读多写少的环境,支持并发读,可以保证数据的最终一致性(不保证实时性,不保证每次读的数据都是最新的)

  特点:

  • 支持高并发读取:CopyOnWriteArrayList允许多个线程同时读取数据,读取操作不需要加锁,因此可以实现高效的并发读取。
  • 写操作的代价较高:当进行写操作时,CopyOnWriteArrayList会创建一个新的数组,并将原有数组的内容复制到新数组中,然后进行写操作。因此,写操作的代价较高,适用于读操作远远多于写操作的场景。

2.1 内部实现原理

  CopyOnWriteArrayList的内部实现原理主要包括以下几点:

  • 数组:CopyOnWriteArrayList内部使用数组来存储元素。
  • 写时复制:当进行写操作时,CopyOnWriteArrayList会创建一个新的数组,并将原有数组的内容复制到新数组中,然后进行写操作。这种写时复制的机制保证了读操作的线程安全性。

2.2 Copy-On-Write(COW)设计思想

  如果简单的使用读写锁的话,在写锁被获取之后,读写线程被阻塞,只有当写锁被释放后读线程才有机会获取到锁从而读到最新的数据,站在读线程的角度来看,即读线程任何时候都是获取到最新的数据,满足数据实时性,但是读取速度会被限制。

  COW牺牲数据实时性满足数据的最终一致性,以获取更快的读取。

  COW通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。

  对CopyOnWrite容器进行并发的读的时候,不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,延时更新的策略是通过在写的时候针对的是不同的数据容器来实现的,放弃数据实时性达到数据的最终一致性。

2.3 实操

  CopyOnWriteArrayList提供了一系列的并发操作方法,常用的包括:

  • add(element):向CopyOnWriteArrayList的末尾添加元素。
  • get(index):根据索引获取对应的元素。
  • remove(index):根据索引移除对应的元素。
  • size():返回CopyOnWriteArrayList中元素的数量。
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListExample {
    public static void main(String[] args) {
        // 创建CopyOnWriteArrayList实例
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

        // 并发添加元素
        list.add("element1");
        list.add("element2");
        list.add("element3");

        // 并发读取元素
        String element1 = list.get(0);
        String element2 = list.get(1);
        String element3 = list.get(2);

        // 并发移除元素
        list.remove(0);

        // 获取元素数量
        int size = list.size();

        System.out.println("element1: " + element1);
        System.out.println("element2: " + element2);
        System.out.println("element3: " + element3);
        System.out.println("size: " + size);
    }
}



三、Map

🚣3、ConcurrentSkipListMap


  ConcurrentSkipListMap是Java中的线程安全的有序映射表实现,它具有以下特点:

  • 有序映射、范围查询:ConcurrentSkipListMap维护了一个有序的键值对映射关系,根据键的顺序,它可以提供范围查询、按键排序等功能。

  • 并发安全:多个线程可以同时对其进行读取和写入操作而不需要额外的同步措施。它使用了一些并发控制机制,如CAS(Compare and Swap)操作和锁分段技术,来保证并发访问的正确性和一致性。

  • 高效性能:它内部使用了跳表数据结构,通过层级索引的方式提供了快速的查找和插入操作。同时,它采用了锁分段技术,不同的线程可以并发地访问不同的段,减少了锁的竞争,提高了并发性能。

  • 不允许空键:ConcurrentSkipListMap不允许插入空键(null key),因为它使用键的顺序来维护有序性。如果需要使用空键,可以考虑使用ConcurrentHashMap。

3.1 跳表(Skip List)

  ConcurrentSkipListMap的内部实现原理主要依赖于跳表(Skip List)数据结构来实现高效的并发操作和有序性。

  跳表(Skip List)是一种基于链表的数据结构,通过在原始链表上建立多级索引,以提高查找效率,特别适用于需要频繁的插入和查找操作的有序集合。它的实现相对简单,并且在实际应用中具有广泛的应用,如数据库索引、缓存实现等。

在这里插入图片描述

  1. 结构组成:跳表由多个层级组成,每个层级都是一个有序的链表。最底层是原始链表,每个元素按照顺序连接。上面的每个层级都是原始链表的子集,其中的元素通过指针连接到下一层级的元素。

  2. 索引层级:跳表的每个层级都是原始链表的一个子集。顶层包含最少的元素,而底层包含所有的元素。每个元素在每个层级中都有一个指针,指向下一个层级中与其相邻的元素。

  3. 跳跃操作:跳表的名称来源于它的跳跃操作。通过索引层级,跳表可以在查找时跳过一些元素,从而快速定位目标元素。这种跳跃操作类似于二分查找,但可以在更高的层级上进行跳跃。

  4. 插入和删除操作:插入和删除操作在跳表中相对容易。在插入元素时,需要在每个层级中找到正确的位置,并更新相应的指针。删除操作类似,需要更新相应的指针。这些操作的时间复杂度通常为O(log n),其中n是元素的数量。

  5. 查找操作:跳表的查找操作非常高效。通过跳跃操作,可以在O(log n)的时间复杂度内找到目标元素。跳表的查找效率与平衡二叉搜索树相当,但实现起来相对简单。

  6. 空间复杂度:跳表的空间复杂度为O(n),其中n是元素的数量。这是因为跳表需要额外的索引层级来提高查找效率。

3.2 并发操作方法

  ConcurrentSkipListMap提供了一系列的并发操作方法,常用的包括:

  • put(key, value):向映射表中添加键值对。
  • get(key):根据键获取对应的值。
  • remove(key):根据键移除对应的键值对。
  • size():返回映射表中键值对的数量。
import java.util.concurrent.ConcurrentSkipListMap;

public class ConcurrentSkipListMapExample {
    public static void main(String[] args) {
        // 创建ConcurrentSkipListMap实例
        ConcurrentSkipListMap<Integer, String> map = new ConcurrentSkipListMap<>();

        // 并发添加键值对
        map.put(3, "value3");
        map.put(1, "value1");
        map.put(2, "value2");

        // 并发获取值
        String value1 = map.get(1);
        String value2 = map.get(2);

        // 并发移除键值对
        map.remove(3);

        // 获取映射表大小
        int size = map.size();

        System.out.println("value1: " + value1);
        System.out.println("value2: " + value2);
        System.out.println("size: " + size);
    }
}

四、队列

🚣4、ConcurrentLinkedQueue


  ConcurrentLinkedQueue是基于链表实现的数据安全队列。

  • 支持高并发操作:ConcurrentLinkedQueue使用了无锁的算法,可以实现高效的并发操作。
  • 无界队列:ConcurrentLinkedQueue没有容量限制,可以根据需要动态地添加元素。

4.1 内部实现原理

  ConcurrentLinkedQueue的内部实现原理主要包括以下几点:

  • 链表:ConcurrentLinkedQueue内部使用链表来存储元素。
  • CAS操作:ConcurrentLinkedQueue使用CAS(Compare and Swap)操作来实现并发操作,避免了使用传统的锁机制带来的性能开销。

链表结点结构:

private static class Node<E> {
        volatile E item;
        volatile Node<E> next;
		.......
}

  Node节点主要包含了两个域:一个是数据域item,另一个是next指针,用于指向下一个节点从而构成链式队列。并且都是用volatile进行修饰的,以保证内存可见性。另外ConcurrentLinkedQueue含有这样两个成员变量,说明ConcurrentLinkedQueue通过持有头尾指针进行管理队列。

private transient volatile Node<E> head;
private transient volatile Node<E> tail;

在这里插入图片描述

CAS操作:
  ConcurrentLinkedQueue对Node的CAS操作有这样几个:

//更改Node中的数据域item	
boolean casItem(E cmp, E val) {
    return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
//更改Node中的指针域next
void lazySetNext(Node<E> val) {
    UNSAFE.putOrderedObject(this, nextOffset, val);
}
//更改Node中的指针域next
boolean casNext(Node<E> cmp, Node<E> val) {
    return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}

4.2 并发操作方法

  ConcurrentLinkedQueue提供了一系列的并发操作方法,常用的包括:

  • offer(element):向队列尾部添加元素。
  • poll():从队列头部获取并移除元素。
  • peek():获取队列头部的元素,但不移除。
  • size():返回队列中元素的数量。
import java.util.concurrent.ConcurrentLinkedQueue;

public class ConcurrentLinkedQueueExample {
    public static void main(String[] args) {
        // 创建ConcurrentLinkedQueue实例
        ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();

        // 并发添加元素
        queue.offer("element1");
        queue.offer("element2");
        queue.offer("element3");

        // 并发获取并移除元素
        String element1 = queue.poll();
        String element2 = queue.poll();

        // 并发获取队列头部元素
        String head = queue.peek();

        // 获取队列大小
        int size = queue.size();

        System.out.println("element1: " + element1);
        System.out.println("element2: " + element2);
        System.out.println("head: " + head);
        System.out.println("size: " + size);
    }
}

🚣5、BlockingQueue(阻塞队列)


  BlockingQueue是Java中的线程安全的阻塞队列实现,它具有以下特点:

  • 支持并发操作:BlockingQueue可以在多个线程之间安全地传递数据。
  • 阻塞操作:当队列为空时,获取元素的操作会被阻塞;当队列已满时,添加元素的操作会被阻塞。
  • 先进先出,全自动,不用管什么时候阻塞

在这里插入图片描述

5.1 内部实现原理

  BlockingQueue的内部实现原理主要依赖于同步器(如ReentrantLock、Condition、Semaphore等)来实现阻塞操作的控制。

5.2 阻塞队列和非阻塞队列

  阻塞队列适用于需要线程之间同步和协作的场景,而非阻塞队列适用于对并发性能要求较高的场景。

  阻塞队列(Blocking Queue)是一种线程安全的队列,当队列为空时,从队列中获取元素的操作会被阻塞,直到队列中有可用元素;当队列已满时,向队列中添加元素的操作会被阻塞,直到队列有空闲位置。阻塞队列提供了一种简单而有效的方式来实现线程之间的同步和协作。

  常见的阻塞队列实现包括ArrayBlockingQueueLinkedBlockingQueueArrayBlockingQueue使用数组实现,具有固定的容量;LinkedBlockingQueue使用链表实现,可以选择是否有容量限制。

  阻塞队列的特点:

  1. 线程安全:阻塞队列是线程安全的,多个线程可以同时进行入队和出队操作,而不需要额外的同步措施。
  2. 阻塞操作:当队列为空时,获取元素的操作会被阻塞,直到队列中有可用元素;当队列已满时,添加元素的操作会被阻塞,直到队列有空闲位置。
  3. 同步和协作:阻塞队列提供了一种简单的机制来进行线程之间的同步和协作。线程可以在队列上等待或者唤醒其他线程,以实现线程之间的协调。

  非阻塞队列(Non-blocking Queue)是一种不会阻塞线程的队列实现,当队列满时,添加元素的操作会立即返回失败;当队列为空时,获取元素的操作会立即返回空值。非阻塞队列通常使用原子操作和无锁算法来实现。

  常见的非阻塞队列实现包括ConcurrentLinkedQueueLinkedTransferQueueConcurrentLinkedQueue使用链表实现,适用于高并发场景;LinkedTransferQueueLinkedBlockingQueue的扩展,提供了更高级的操作和功能。

  非阻塞队列的特点:

  1. 非阻塞操作:非阻塞队列的操作不会阻塞线程,当队列满时,添加元素的操作会立即返回失败;当队列为空时,获取元素的操作会立即返回空值。
  2. 并发性能:非阻塞队列通常使用原子操作和无锁算法实现,可以在高并发环境下提供较好的性能。
  3. 无等待性:一些非阻塞队列实现提供无等待(wait-free)的保证,即任意时刻都有至少一个线程可以继续进行操作,而不会被其他线程阻塞。

5.3 使用场景和示例

  BlockingQueue提供了一系列的并发操作方法,常用的包括:

  • put(element):向队列尾部添加元素,如果队列已满,则阻塞等待。
  • take():从队列头部获取并移除元素,如果队列为空,则阻塞等待。
  • offer(element, timeout, unit):向队列尾部添加元素,如果队列已满,则阻塞一段时间,超时后返回false。
  • poll(timeout, unit):从队列头部获取并移除元素,如果队列为空,则阻塞一段时间,超时后返回null。

示例代码:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueExample {
    public static void main(String[] args) {
        // 创建BlockingQueue实例
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);

        // 生产者线程
        Thread producerThread = new Thread(() -> {
            try {
                // 向队列中添加元素
                queue.put("element1");
                queue.put("element2");
                queue.put("element3");
                System.out.println("Producer: elements added to the queue.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 消费者线程
        Thread consumerThread = new Thread(() -> {
            try {
                // 从队列中获取元素
                String element1 = queue.take();
                String element2 = queue.take();
                String element3 = queue.take();
                System.out.println("Consumer: elements taken from the queue.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 启动生产者和消费者线程
        producerThread.start();
        consumerThread.start();
    }
}



🚣6、ConcurrentLinkedDeque(双端队列)


  ConcurrentLinkedDeque是Java中的线程安全的双端队列实现,它具有以下特点:

  • 支持高并发操作:ConcurrentLinkedDeque使用了无锁的算法,可以实现高效的并发操作。
  • 双端队列:ConcurrentLinkedDeque可以在队列的两端进行元素的插入和删除操作。

6.1 内部实现原理

  ConcurrentLinkedDeque的内部实现原理主要依赖于链表数据结构来实现高效的并发操作。

6.2 并发操作方法

  ConcurrentLinkedDeque提供了一系列的并发操作方法,常用的包括:

  • addFirst(element):在队列的头部添加元素。
  • addLast(element):在队列的尾部添加元素。
  • removeFirst():移除并返回队列头部的元素。
  • removeLast():移除并返回队列尾部的元素。
  • peekFirst():返回队列头部的元素,但不移除。
  • peekLast():返回队列尾部的元素,但不移除。
  • size():返回队列中元素的数量。
import java.util.concurrent.ConcurrentLinkedDeque;

public class ConcurrentLinkedDequeExample {
    public static void main(String[] args) {
        // 创建ConcurrentLinkedDeque实例
        ConcurrentLinkedDeque<Integer> deque = new ConcurrentLinkedDeque<>();

        // 并发添加元素
        deque.addFirst(3);
        deque.addLast(1);
        deque.addLast(2);

        // 并发移除元素
        int first = deque.removeFirst();
        int last = deque.removeLast();

        // 获取队列头部和尾部的元素
        int peekFirst = deque.peekFirst();
        int peekLast = deque.peekLast();

        // 获取队列大小
        int size = deque.size();

        System.out.println("First: " + first);
        System.out.println("Last: " + last);
        System.out.println("Peek First: " + peekFirst);
        System.out.println("Peek Last: " + peekLast);
        System.out.println("Size: " + size);
    }
}

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

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

相关文章

C语言第十课----------------扫雷----------数组的经典练手题

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; &#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382;…

力扣120.三角形最小路径和(动态规划)

/*** author Limg* date 2022/08/09* 给定一个三角形 triangle &#xff0c;找出自顶向下的最小路径和。* 每一步只能移动到下一行中相邻的结点上。* 相邻的结点在这里指的是下标与上一层结点下标相同或者等于上一层结点下标 1 的两个结点。* 也就是说&#xff0c;如果正位于当…

(MVC)SpringBoot+Mybatis+Mapper.xml

前言&#xff1a;本篇博客主要对MVC架构、Mybatis工程加深下理解&#xff0c;前面写过一篇博客&#xff1a;SprintBoothtml/css/jsmybatis的demo&#xff0c;里面涉及到了Mybatis的应用&#xff0c;此篇博客主要介绍一种将sql语句写到了配置文件里的方法&#xff0c;即Mybatis里…

AVL树(二叉搜索树)

AVL树 1.1 AVL树的概念1.2 AVL树节点的定义1.3 AVL树的旋转1.3.1 右旋&#xff08;右单旋&#xff09;1.3.2 左旋&#xff08;左单旋&#xff09;1.3.3 左右双旋&#xff08;先左单旋再右单旋&#xff09;1.3.4 右左双旋&#xff08;先右单旋再左单旋&#xff09; 1.4 AVL树的插…

进程 的初识

程序和进程有什么区别 程序是静态的概念&#xff0c;gcc xxx.c -o pro 磁盘中生成的文件&#xff0c;叫做程序。进程是程序的一次运行活动&#xff0c;通俗点的意思就是程序跑起来了&#xff0c;系统中就多了一个进程。 如何查看系统中有哪些进程 使用 ps 指令&#xff08;完整…

❤ vue组件的生命周期

❤ vue组件的生命周期 介绍 在vue组件中&#xff0c;生命周期指的是从组件创建开始&#xff0c;到组件销毁&#xff0c;所经历的整个过程&#xff1b;在这个过程中的一些不同的阶段&#xff0c;vue会调用指定的一些组件方法。基本生命周期函数有下面几个阶段&#xff1a;创建…

Python中的dataclass:简化数据类的创建

Python中的dataclass是一个装饰器&#xff0c;用于自动添加一些常见的方法&#xff0c;如构造函数、__repr__、__eq__等。它简化了创建数据类的过程&#xff0c;减少了样板代码&#xff0c;提高了代码的可读性和可维护性。有点类似java里面的Java Bean。 让我们看一个简单的例子…

将.doc文档的默认打开方式从WPS修改为word office打开方式的具体方法(以win 10 操作系统为例)

将.doc文档的默认打开方式从WPS修改为word office打开方式的具体方法&#xff08;以win 10 操作系统为例&#xff09; 随着近几年WPS软件的不断完善和丰富&#xff0c;在某些方面取得了具有特色的优势。在平时编辑.doc文档时候也常常用到wps软件&#xff0c;不过WPS文献也存在…

【分布式技术专题】RocketMQ延迟消息实现原理和源码分析

痛点背景 业务场景 假设有这么一个需求&#xff0c;用户下单后如果30分钟未支付&#xff0c;则该订单需要被关闭。你会怎么做&#xff1f; 之前方案 最简单的做法&#xff0c;可以服务端启动个定时器&#xff0c;隔个几秒扫描数据库中待支付的订单&#xff0c;如果(当前时间-订…

EasyPoi导出 导入(带校验)简单示例 EasyExcel

官方文档 : http://doc.wupaas.com/docs/easypoi pom的引入: <!-- easyPoi--><dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><version>4.0.0</version></dep…

分布式协调组件Zookeeper

Zookeeper介绍 什么是Zookeeper ZooKeeper 是⼀种分布式协调组件&#xff0c;用于管理大型主机。在分布式环境中协调和管理服务是一个复杂的过程。ZooKeeper 通过其简单的架构和 API 解决了这个问题。ZooKeeper 允许开发人员专注于核心应用程序逻辑&#xff0c;而不必担心应用…

【Linux】多线程——线程引入 | 线程控制

文章目录 一、Linux多线程1. 线程概念2. 线程创建3. 线程和进程4. 线程的优缺点 二、线程控制1. 线程创建2. 线程终止3. 线程等待4. 线程分离5. 线程局部存储 三、线程封装 一、Linux多线程 一级页表和二级页表都是key/val模型&#xff0c;一级页表的key是第一份的10个比特位&a…

(统计学习方法|李航)第一章统计学习方法概论——四五六节模型评估与模型选择,正则化与交叉验证,泛化能力

一&#xff0c;模型评估与模型选择 1.训练误差与测试误差 假如我们有100个数据。80条记录给训练集&#xff0c;10条记录给测试集&#xff0c;10条记录给验证集 先在训练集中训练模型&#xff0c; 再在验证集上测试看哪种模型更拟合 最后用测试集算出成绩 表示决策函数 模型…

数据清理在数据科学中的重要性

什么是数据清理&#xff1f; 推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可编辑的3D应用场景 在数据科学中&#xff0c;数据清理是识别不正确数据并修复错误的过程&#xff0c;以便最终数据集可供使用。错误可能包括重复字段、格式不正确、字段不完整、数据不相关或不准…

基于kettle实现pg数据定时转存mongodb

mogodb 待创建 基于kettle实现pg数据定时转存mongodb_kettle 实时迁移 mongodb_呆呆的私房菜的博客-CSDN博客

链表和哈希Set

1 LinkedList集合类 LinkedList集合类底层是使用双向链表实现的&#xff0c;相较于ArrayList&#xff0c;更方便进行增删操作。 在增删查改方面&#xff0c;新增了头尾操作&#xff0c;比如从头部插入、尾部插入、头部删除、尾部删除、头部查询和尾部查询等操作。由于有头尾的…

SpringCloud实用篇3----Docker

1.初识Docker 1.1 什么是Docker 微服务虽然具备各种各样的优势&#xff0c;但服务的拆分通用给部署带来了很大的麻烦。 分布式系统中&#xff0c;依赖的组件非常多&#xff0c;不同组件之间部署时往往会产生一些冲突。在数百上千台服务中重复部署&#xff0c;环境不一定一致…

gitblit windows部署

1.官网下载 往死慢&#xff0c;我是从百度找的1.9.1&#xff0c;几乎就是最新版 http://www.gitblit.com/ 2.解压 下载下来是一个zip压缩包&#xff0c;直接解压即可 3.配置 3.1.配置资源库路径 找到data文件下的gitblit.properties文件&#xff0c;用Notepad打开 **注意路…

云原生可观测框架 OpenTelemetry 基础知识(架构/分布式追踪/指标/日志/采样/收集器)...

什么是 OpenTelemetry&#xff1f; OpenTelemetry 是一个开源的可观测性框架&#xff0c;由云原生基金会(CNCF)托管。它是 OpenCensus 和 OpenTracing 项目的合并。旨在为所有类型的可观测信号(如跟踪、指标和日志)提供单一标准。 https://opentelemetry.iohttps://www.cncf.io…

微服务Eureka注册中心

目录 一、Eureka的结构和作用 二、搭建eureka-server 三、服务注册 四、服务发现 假如我们的服务提供者user-service部署了多个实例&#xff0c;如图&#xff1a; 存在的问题&#xff1a; order-service在发起远程调用的时候&#xff0c;该如何得知user-service实例的ip地址…