线程系列 7 - JUC高并发容器类

线程系列 7 - JUC高并发容器类

  • 1、JUC高并发容器
    • 1.1、为什么需要JUC高并发容器
    • 1.2、什么是 JUC 高并发容器
    • 1.3、CopyOnWriteArrayList
    • 1.4、BlockingQueue
      • 1.4.1、阻塞队列的常用方法
      • 1.4.2、ArrayBlockingQueue
      • 1.4.3、LinkedBlockingQueue
      • 1.4.4、DelayQueue
      • 1.4.5、PriorityBlockingQueue
      • 1.4.6、SynchronousQueue
    • 1.5、ConcurrentHashMap

1、JUC高并发容器

 

1.1、为什么需要JUC高并发容器

 
       早期的同步容器一般使用 Vector、HashTable、java.util.Collections ,这些同步容器实现线程安全的方式是:在需要同步访问的方法上添加关键字synchronized。

       synchronized 在线程没有发生争用的场景下处于偏向锁的状态,其性能是非常高的。但是,一旦发生了线程争用,synchronized 会由偏向锁膨胀成重量级锁,在抢占和释放时发生 CPU 内核态与用户态切换,所以削弱了并发性,降低了吞吐量,而且会严重影响性能。正因为如此,JUC提供了一套高并发容器类。
 

1.2、什么是 JUC 高并发容器

 

JUC高并发容器是基于非阻塞算法(或者无锁编程算法)实现的容器类,无锁编程算法主要通过 CAS(保障操作的原子性) + Volatile(保障变量内存的可见性)组合实现。

无锁编程算法的主要优点如下:

  • ① 开销较小:不需要在内核态和用户态之间切换进程。
  • ② 读写不互斥:只有写操作需要使用基于CAS机制的乐观锁,读读操作之间可以不用互斥。

 
JUC包中提供了List、Set、Queue、Map各种类型的高并发容器。

  • List

    JUC包中的高并发List主要有CopyOnWriteArrayList,对应的基础容器为ArrayList。

    CopyOnWriteArrayList相当于线程安全的ArrayList,它实现了List接口。在读多写少的场景中,其性能远远高于ArrayList的同步包装容器。

  • Set

    JUC包中的Set主要有 CopyOnWriteArraySet 和 ConcurrentSkipListSet 。

    • CopyOnWriteArraySet继承自AbstractSet类,对应的基础容器为 HashSet 。其内部组合了一个CopyOnWriteArrayList对象,它的核心操作是基于CopyOnWriteArrayList实现的。
    • ConcurrentSkipListSet是线程安全的有序集合,对应的基础容器为TreeSet。它继承自AbstractSet,并实现了NavigableSet接口。ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的。
  • Map

    JUC包中Map主要有 ConcurrentHashMap 和 ConcurrentSkipListMap 。

    • ConcurrentHashMap 对应的基础容器为HashMap。JDK 6中的ConcurrentHashMap 采用一种更加细粒度的“分段锁”加锁机制,JDK8中采用CAS无锁算法。
    • ConcurrentSkipListMap 对应的基础容器为TreeMap。其内部的 Skip List(跳表)结构是一种可以代替平衡树的数据结构,默认是按照Key值升序的。
  • Queue

    JUC包中的 Queue 的实现类包括三类:单向队列、双向队列和阻塞队列。

    • ConcurrentLinkedQueue 是基于列表实现的单向队列,按照 FIFO(先进先出)原则对元素进行排序。新元素从队列尾部插入,而获取队列元素则需要从队列头部获取。
    • ConcurrentLinkedDeque 是基于链表的双向队列,但是该队列不允许null元素。作为双向队列,ConcurrentLinkedDeque 可以当作“栈”来使用,并且高效地支持并发环境。
    • ArrayBlockingQueue:基于数组实现的可阻塞的FIFO队列。
    • LinkedBlockingQueue:基于链表实现的可阻塞的FIFO队列。
    • PriorityBlockingQueue:按优先级排序的队列。
    • DelayQueue:按照元素的Delay时间进行排序的队列。
    • SynchronousQueue:无缓冲等待队列。

 

1.3、CopyOnWriteArrayList

 
       JUC包中的高并发 List 主要有 CopyOnWriteArrayList ,对应的基础容器为ArrayList。CopyOnWriteArrayList 相当于线程安全的 ArrayList,它实现了List接口。在读多写少的场景中,其性能远远高于 ArrayList 的同步包装容器

写时复制(Copy On Write,COW)的主要 优点 是:如果没有修改器去修改资源,就不会创建副本,因此多个访问器可以共享同一份资源。其 核心思想 是:如果有多个访问器(Accessor)访问一个资源(如内存或者磁盘上的数据存储),它们会共同获取相同的指针指向相同的资源,只要有一个修改器(Mutator)需要修改该资源,系统就会复制一份专用副本(Private Copy)给该修改器,而其他访问器所见到的最初资源仍然保持不变,修改的过程对其他访问器都是透明的(Transparently)。

  • CopyOnWriteArrayList 的优点

    CopyOnWriteArrayList 有一个显著的优点,那就是 读取、遍历操作不需要同步,速度会非常快 。所以,CopyOnWriteArrayList适用于读操作多、写操作相对较少的场景(读多写少),比如可以在进行“黑名单”拦截时使用CopyOnWriteArrayList。

  • CopyOnWriteArrayList 和 ReentrantReadWriteLock 的比较
    CopyOnWriteArrayList 和 ReentrantReadWriteLock 读写锁的思想非常类似,即 读读共享写写互斥读写互斥写读互斥。但是前者相比后者的更进一步:为了将读取的性能发挥到极致,CopyOnWriteArrayList 读取是完全不用加锁的,而且写入也不会阻塞读取操作,只有写入和写入之间需要进行同步等待,读操作的性能得到大幅度提升

CopyOnWriteArrayList的写入操作 add() 方法,在执行时加了独占锁以确保只能有一个线程进行写入操作,避免多线程写的时候会复制出多个副本。在每次进行添加操作时,CopyOnWriteArrayList底层都是重新复制一份数组,再往新的数组中添加新元素,待添加完了,再将新的array引用指向新的数组。当add()操作完成后,array的引用就已经指向另一个存储空间了。
 
JDK8 源码:

   /*** Appends the specified element to the end of this list.** @param e element to be appended to this list* @return {@code true} (as specified by {@link Collection#add})*/public boolean add(E e) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;Object[] newElements = Arrays.copyOf(elements, len + 1);newElements[len] = e;setArray(newElements);return true;} finally {lock.unlock();}}

每次添加元素的时候都会重新复制一份新的数组,这样增加了内存的开销,如果容器的写操作比较频繁,那么其开销就比较大。所以,在实际应用的时候,CopyOnWriteArrayList并不适合进行添加操作。

 
使用CopyOnWriteArrayList容器,可以在进行元素迭代的同时,进行元素添加操作。代码示例:

import lombok.extern.slf4j.Slf4j;import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;public class ListDemo {public static void main(String[] args) throws InterruptedException {List<String> notSafeList = Arrays.asList("a", "b", "c");//创建一个CopyOnWriteArrayList队列List<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();copyOnWriteArrayList.addAll(notSafeList);//并发执行目标ListThread listThread = new ListThread(copyOnWriteArrayList);for (int i = 0; i < 10; i++) {new Thread(listThread, "线程" + i).start();}//主线程等待TimeUnit.SECONDS.sleep(2);}
}
@Slf4j
class ListThread implements Runnable{// 并发操作的 目标集合List<String>  targetList = null;public ListThread (List<String> list) {this.targetList = list;}@Overridepublic void run() {Iterator<String> iterator = targetList.iterator();//迭代操作while (iterator.hasNext()) {// 在迭代操作时,进行列表的修改String threadName = Thread.currentThread().getName();targetList.add(threadName);log.info("开始往同步队列加入线程名称:{}", threadName);}}
}

 

1.4、BlockingQueue

 
       BlockingQueue的常用实现类有 ArrayBlockingQueueDelayQueueLinkedBlockingQueuePriorityBlockingQueueSynchronousQueue 等。

阻塞队列与普通队列(ArrayDeque等)之间的最大不同点,在于阻塞队列提供了阻塞式的添加和删除方法。

  • 阻塞添加
    阻塞添加是指当阻塞队列元素已满时,队列会阻塞添加元素的线程,直到队列元素不满时,才重新唤醒线程执行元素添加操作。
  • 阻塞删除
    阻塞删除是指在队列元素为空时,删除队列元素的线程将被阻塞,直到队列不为空时,才重新唤醒删除线程,再执行删除操作。

 

1.4.1、阻塞队列的常用方法

 
阻塞队列三类方法的特征:

方法类别抛出异常特殊值阻塞限时阻塞
添 加add(e)offer(e)put(e)offer(e,time,unit)
删 除remove()poll()take()poll(time,unit)
获取元素element()peek()不可用不可用

特征说明:

  • 抛出异常 :如果试图的操作无法立即执行,就抛出一个异常。

  • 特殊值 :如果尝试的操作无法立即执行,就返回一个特定的值(通常是 true/false)。

  • 阻塞 :如果尝试的操作无法立即执行,该方法的调用就会发生阻塞,直到能够执行。

  • 限时阻塞 :如果尝试的操作无法立即执行,该方法的调用就会发生阻塞,直到能够执行,但等待时间不会超过设置的上限值。

public interface BlockingQueue<E> extends Queue<E> {/*** 将指定的元素添加到此队列的尾部* 在成功时返回true,如果此队列已满,就抛出**/IllegalStateException boolean add(E e); /*** 非阻塞式添加:将指定的元素添加到此队列的尾部(如果立即可行且不会超过该队列的容量)* 如果该队列已满,就直接返回**/boolean offer(E e);/*** 限时阻塞式添加:将指定的元素添加到此队列的尾部* 如果该队列已满,那么在到达指定的等待时间之前,添加线程会阻塞,等待可用的时间,该方法可中断**/boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException; /*** 阻塞式添加:将指定的元素添加到此队列的尾部,如果该队列已满,就一直等待(阻塞)**/void put(E e) throws InterruptedException; /*** 从此队列中移除指定元素,返回删除是否成功**/ boolean remove(Object o); /*** 非阻塞式删除:获取并移除此队列的头部,如果没有元素就直接返回null(空)**/E poll() throws InterruptedException;     /*** 阻塞式删除:获取并移除此队列的头部,如果没有元素就等待(阻塞)* 直到有元素,将唤醒等待线程执行该操作**/E take() throws InterruptedException; /*** 限时阻塞式删除:获取并移除此队列的头部,在指定的等待时间前一直等* 待获取元素,超过时间,方法将结束**/E poll(long timeout, TimeUnit unit) throws InterruptedException; /*** 获取但不移除此队列的头元素,没有则抛出异常NoSuchElementException**/ E element(); /*** 获取但不移除此队列的头元素,如果此队列为空,就返回null**/E peek(); }

 

1.4.2、ArrayBlockingQueue

 
       ArrayBlockingQueue 是一个常用的阻塞队列,是基于数组实现的,其内部使用一个定长数组来存储元素。除了一个定长数组外,ArrayBlockingQueue 内部还保存着两个整型变量,分别标识着队列的头部和尾部在数组中的位置。ArrayBlockingQueue 的添加和删除操作共用同一个锁对象,由此意味着添加和删除无法并行运行。
 
为什么ArrayBlockingQueue比LinkedBlockingQueue更加常用?

       ArrayBlockingQueue在添加或删除元素时,不会产生或销毁任何额外的Node(节点)实例。而 LinkedBlockingQueue 会生成一个额外的 Node 实例。在长时间、高并发处理大批量数据的场景中,LinkedBlockingQueue 产生的额外 Node 实例会加大系统的GC压力。
 

构造函数:

 /*** 默认非公平阻塞队列* @param capacity 这个队列容量* @throws IllegalArgumentException if {@code capacity < 1}*/public ArrayBlockingQueue(int capacity) {this(capacity, false);}/**** @param capacity 这个队列容量* @param fair 是否公平阻塞队列,如果true然后队列访问线程阻塞在插入或移除,* 以FIFO的顺序处理;如果 false存取顺序是不确定的。 * @throws IllegalArgumentException 如果 capacity小于 c.size(),或小于1。 */public ArrayBlockingQueue(int capacity, boolean fair) {if (capacity <= 0)throw new IllegalArgumentException();this.items = new Object[capacity];// 根据fair参数构造公平锁lock = new ReentrantLock(fair);// 有元素加入,队列为非空notEmpty = lock.newCondition();// 有元素被取出,队列为未满notFull =  lock.newCondition();}/**** @param capacity 这个队列容量 * @param fair 是否公平阻塞队列,如果true然后队列访问线程阻塞在插入或移除,* 以FIFO的顺序处理;如果 false存取顺序是不确定的。 * @param c  最初包含元素的集合 * @throws IllegalArgumentException 如果 capacity小于 c.size(),或小于1。 * @throws NullPointerException 如果指定集合或其任何元素都是空的*/public ArrayBlockingQueue(int capacity, boolean fair,Collection<? extends E> c) {this(capacity, fair);final ReentrantLock lock = this.lock;lock.lock(); // Lock only for visibility, not mutual exclusiontry {final Object[] items = this.items;int i = 0;try {for (E e : c)items[i++] = Objects.requireNonNull(e);} catch (ArrayIndexOutOfBoundsException ex) {throw new IllegalArgumentException();}count = i;putIndex = (i == capacity) ? 0 : i;} finally {lock.unlock();}}

代码示例:

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ArrayBlockingQueue;/*** 数据缓存区*/
@Slf4j
public class DataBuffer<T> {// 数据缓存区,指定阻塞队列的长度为 10public static final int MAX_AMOUNT = 10;// 使用阻塞队列保存数据private ArrayBlockingQueue<T> queue = new ArrayBlockingQueue<>(MAX_AMOUNT);//  向数据区增加一个元素,委托给阻塞队列public void add(T t) throws InterruptedException {// 直接委托给 ArrayBlockingQueue 阻塞队列去添加元素// put(T t) 方法,阻塞式添加:将指定的元素添加到此队列的尾部,如果该队列已满,就一直等待(阻塞)queue.put(t);log.info("数据缓存区添加元素成功,{}", t);}// 从数据区取出一个商品,委托给阻塞队列public T fetch() throws InterruptedException {// 取出操作直接委托给 ArrayBlockingQueue 阻塞队列// take() 方法 是阻塞式删除:获取并移除此队列的头部,如果没有元素就等待(阻塞)T t = queue.take();log.info("数据缓存区取出元素成功,{}", t);return t;}}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @description: 生产的产品*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Good implements Cloneable {private String name;@Overridepublic Good clone() {Good good = null;try {good = (Good) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return good;}
}
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;/*** @description: 生产者*/
@Slf4j
public class Producer implements Runnable {// 生产次数计数器static final AtomicInteger TURN = new AtomicInteger(1);// 生产的动作Callable action = null;// 生产者生产,默认耗时2sprivate int gap = 2;public Producer(Callable action, int gap) {this.action = action;this.gap = gap;}@Overridepublic void run() {// 这里一直生产while (true) {try {//执行生产动作Object out = action.call();// 模拟生产者生产耗时TimeUnit.SECONDS.sleep(gap);//增加生产轮次TURN.incrementAndGet();//输出生产的结果if (null != out) {log.info("线程{}->第{}次生产:{}", Thread.currentThread().getName(), TURN.get(), out);}} catch (Exception e) {e.printStackTrace();}}}
}
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;@Slf4j
public class Consumer extends Thread {//消费总次数计数器static final AtomicInteger TURN = new AtomicInteger(0);//消费的动作Callable action = null;//消费默认消费耗时3sint gap = 3;public Consumer(Callable action, int gap) {this.action = action;this.gap = gap;}@Overridepublic void run() {while (true) {//增加消费次数TURN.incrementAndGet();try {//执行消费动作Object out = action.call();if (null != out) {log.info("线程{}->第{}次消费:{}", Thread.currentThread().getName(),TURN.get(), out);}// 模拟消费者消费耗时TimeUnit.SECONDS.sleep(gap);} catch (Exception e) {e.printStackTrace();}}}
}
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;@Slf4j
public class ArrayBlockingQueuePetStore {public static void main(String[] args) {// 新建一个缓存区DataBuffer<Good> dataBuffer = new DataBuffer<>();Good goodBase = new Good("蓝莓");final AtomicInteger goodCount = new AtomicInteger(0);// 定义生产者Callable<Good> produceAction = () -> {//首先生成一个随机的商品Good good = goodBase.clone();int aa = goodCount.incrementAndGet();good.setName(good.getName() + aa);//将商品加上共享数据区dataBuffer.add(good);return good;};// 定义消费者Callable<Good> consumerAction = () -> {// 从缓存区获取商品Good goods = null;goods = dataBuffer.fetch();return goods;};// 定义线程池ExecutorService pool = Executors.newFixedThreadPool(3);// 执行逻辑,假定共3个线程,其中有2个消费者,但是只有1个生产者final int consumerTotal = 2;final int produceTotal = 3;/** 这个地方如果先执行  pool.submit(new Producer(produceAction, 2)); 循环,* 会导致,生产者任务数瞬间达到线程池 pool 的最大线程数3,消费者者任务进入pool的 LinkedBlockingQueue* 生产者Producer 的 run.while 会导致一直占用线程* 然后生产的任务瞬间达到缓存区dataBuffer阻塞队列queue的最大长度10,并继续put,出现阻塞。* 由于 ArrayBlockingQueue 的添加和删除操作共用同一个锁对象,且 queue 无法消费,而 put 又处于堵塞,* 所以这段程序一直会堵塞下去 。 可以尝试先执行消费者的遍历逻辑,或pool 的最大线程数大于 生产者的任务数**/for (int i = 0; i < produceTotal; i++) {//生产者线程每生产一个商品,需要1秒pool.submit(new Producer(produceAction, 2));}for (int i = 0; i < consumerTotal; i++) {//消费者线程每消费一个商品,需要两秒pool.submit(new Consumer(consumerAction, 1));}log.info("执行主线程逻辑");pool.shutdown();}
}

 

1.4.3、LinkedBlockingQueue

 

       LinkedBlockingQueue 是基于链表的阻塞队列,其内部也维持着一个数据缓冲队列(该队列由一个链表构成)。LinkedBlockingQueue 对于添加和删除元素,分别采用了独立的锁来控制数据同步,所以在高并发的情况下,生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
 

       在新建一个LinkedBlockingQueue对象时,若没有指定其容量大小,则LinkedBlockingQueue 会默认一个类似无限大小的容量(Integer.MAX_VALUE)。需要注意生产者速度大于消费者速度的场景,也许还没有等到队列满阻塞产生,系统内存就已经被消耗殆尽了。
 

1.4.4、DelayQueue

 

       DelayQueue 中的元素只有当其指定的延迟时间到了,才能够从队列中获取该元素。DelayQueue 是一个没有大小限制的队列。所以往队列中添加数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。

DelayQueue的使用场景较少,常见场景是使用 DelayQueue 来管理一个超时未响应的连接队列。

 

1.4.5、PriorityBlockingQueue

 

       PriorityBlockingQueue 是基于优先级的阻塞队列。其并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。要特别注意:生产者生产数据的速度,绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间,同没有指定长度的 LinkedBlockingQueue 一样。

 

1.4.6、SynchronousQueue

 
       SynchronousQueue 是比较独特的队列,其本身是没有容量大小。举例:如果我放一个数据到队列中,我是不能够立马返回的,我必须等待别人把我放进去的数据消费掉了,才能够返回。对单个消息的响应要求高的场景可以使用SynchronousQueue。

SynchronousQueue 有两种模式:公平模式和非公平模式。

  • 公平模式的SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体现出整体的公平特征。

  • 非公平模式(默认情况)的SynchronousQueue采用非公平锁,同时配合一个LIFO堆栈(TransferStack内部实例)来管理多余的生产者和消费者。对于非公平模式,如果生产者和消费者的处理速度有差距,就很容易出现线程饥渴的情况,即可能出现某些生产者或者消费者的数据永远都得不到处理。

 

1.5、ConcurrentHashMap

 
       ConcurrentHashMap是一个常用的高并发容器类,也是一种线程安全的哈希表。

       ConcurrentHashMap 和同步容器 HashTable 的主要区别在锁的类型和粒度上。

       HashTable 实现同步是利用synchronized关键字进行锁定的,其实是针对整张哈希表进行锁定的,即每次锁住整张表让线程独占,虽然解决了线程安全问题,但是造成了巨大的资源浪费。当一个线程访问HashTable的同步方法时,其他访问HashTable同步方法的线程就会进入阻塞或轮询状态。若有一个线程在调用put()方法添加元素,则其他线程不但不能调用put()方法添加元素,而且不能调用get()方法来获取元素,相当于将所有的操作串行化。所以,HashTable的效率非常低下。

JDK 1.8的ConcurrentHashMap引入红黑树的原因是:链表查询的时间复杂度为O(n),红黑树查询的时间复杂度为O(log(n)),所以在节点比较多的情况下,使用红黑树可以大大提升性能。

 
 
 
线程系列博文:
 
线程系列 1 - 线程基础
线程系列 2 - 并发编程之线程池 ThreadPool 的那些事
线程系列 3 - 关于 CompletableFuture
线程系列 4 - synchronized 和线程间的通信
线程系列 5 - CAS 和 JUC原子类
线程系列 6 - JUC相关的显示锁
 
 
 
 
 
.

 

 

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

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

相关文章

TypeScript快速入门

文章目录 一、初识TypeScript1、安装TypeScript2、Hello TypeScript 二、结合项目---配置1、tsconfig.jsontsconfig.json 重要字段compilerOptions 每个选项的详细说明 2、ts-loader 三、语法1、基本类型2、类型注解3、函数4、接口5、类6、泛型 四、结合项目---vue3结合使用 一…

可观测之调用链Skywalking

简介 分布式系统的应用程序性能监视工具&#xff0c;专为微服务、云原生架构和基于容器&#xff08;Docker、K8s、Mesos&#xff09;架构而设计。提供分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。 多种监控手段。可以通过语言探针和 service mesh 获得监控…

HTTPS连接过程中的中间人攻击

HTTPS连接过程中的中间人攻击 HTTPS连接过程中间人劫持攻击 HTTPS连接过程 https协议就是httpssl/tls协议&#xff0c;如下图所示为其连接过程&#xff1a; HTTPS连接的整个工程如下&#xff1a; https请求&#xff1a;客户端向服务端发送https请求&#xff1b;生成公钥和私…

矩阵置零(力扣)思维 JAVA

给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]] 输入&#xff1a;matrix [[0,1,2,0],[3,4,5,2],[…

[ 华为云 ] 云计算中Region、VPC、AZ 是什么,他们又是什么关系,应该如何抉择

前几天看到一个问答帖&#xff0c;我回答完了才发现这个帖子居然是去年的也没人回复&#xff0c;其中他问了一些华为云的问题&#xff0c;对于其中的一些概念&#xff0c;这里来总结讲解一下&#xff0c;希望对学习华为云的小伙伴有所帮助。 文章目录 区域&#xff08;Region&a…

计算机基础专升本笔记四 计算机系统

计算机基础专升本笔记四 计算机系统 计算机系统 计算机系统由计算机硬件系统和计算机软件系统 组成。且是按照存储程序的方式工作的。计算机硬件就是由各种电子器件按照一定逻辑连接而成&#xff0c;看的见摸得着&#xff0c;是计算机系统的物质基础&#xff0c;计算机软件系统…

# jellyfin安装设置使用散记

jellyfin安装设置使用散记 文章目录 jellyfin安装设置使用散记0 软件简介1 安装2 视频转码问题2.1 局域网转码情况测试&#xff08;不同网段&#xff09;2.2 局域网jellyfin app默认转码问题解决2.3 外网转码情况测试 3 一些坑4 插件5 最后 0 软件简介 Jellyfin 是一个自由的软…

UDS之11服务

11服务&#xff1a; 功能&#xff1a;控制MCU进行重启&#xff0c;重启分为硬重启和软重启&#xff0c;11服务一般代表软重启&#xff0c;虽然它里面有个子服务是硬件重启&#xff0c;这里需要注意下&#xff1b;硬重启在日常工作中一般代表B重启。命令格式&#xff08;请求&am…

LiveGBS流媒体平台GB/T28181功能-视频直播流媒体平台分屏展示设备树分组树记录上次分屏播放记录

LiveGBS视频直播流媒体平台分屏展示设备树分组树记录上次分屏播放记录 1、分屏展示1.1、单屏1.2、四分屏1.3、九分屏1.4、十六分屏 2、分屏记录3、搭建GB28181视频直播平台 1、分屏展示 LiveGBS分屏页面支持&#xff0c;多画面播放&#xff0c;支持单屏、四分屏、九分屏、十六…

python中的os._exit()、sys.exit()和exit()/quit()函数

python中的os._exit()、sys.exit()和exit()/quit()函数 os._exit() 官方文档https://docs.python.org/zh-cn/3/library/os.html#os._exit 语法格式&#xff1a; os._exit(n) 以状态码 n 退出进程&#xff08;process&#xff09;&#xff0c;不会调用清理处理程序&#xf…

GPT-4 模型详细教程

GPT-4&#xff08;Generative Pretrained Transformer 4&#xff09;是 OpenAI 的最新语言生成模型&#xff0c;其在各类文本生成任务中表现优秀&#xff0c;深受开发者和研究者喜爱。这篇教程将帮助你理解 GPT-4 的基本概念&#xff0c;并向你展示如何使用它来生成文本。 什么…

python变量及更新

在Python中&#xff0c;变量可以通过赋值操作符&#xff08;&#xff09;来进行赋值。变量赋值是将一个值或表达式绑定到变量名上。 例如&#xff0c;下面的代码将整数10赋值给变量x&#xff1a; x 10 在这个例子中&#xff0c;x是变量名&#xff0c;10是要赋给x的值。 变量还…

Java-API简析_java.net.Proxy类(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/131881661 出自【进步*于辰的博客】 因为我发现目前&#xff0c;我对Java-API的学习意识比较薄弱…

Linux6.13 Docker LNMP项目搭建

文章目录 计算机系统5G云计算第四章 LINUX Docker LNMP项目搭建一、项目环境1.环境描述2.容器ip地址规划3.任务需求 二、部署过程1.部署构建 nginx 镜像2.部署构建 mysql 镜像3.部署构建 php 镜像4.验证测试 计算机系统 5G云计算 第四章 LINUX Docker LNMP项目搭建 一、项目…

MySQL 索引的优缺点及索引注意事项

MySQL索引是数据库中用于加快数据检索速度的一种数据结构。它在数据库表中的列上创建一个索引&#xff0c;以便数据库可以更快地查找和访问数据。 索引的优缺点 优点&#xff1a; 快速检索&#xff1a;索引可以大大减少数据库查询的时间&#xff0c;特别是在大型表中。通过使…

sqlite3 插入数据

文章目录 需求&#xff0c;操作1.进入sqlite终端2.打开数据库3.执行插入语句。4.查看是否成功 最近有项目在用sqlite3&#xff0c;这个嵌入式数据库&#xff0c;不是很熟练&#xff0c;连个插入数据的语句都得百度哈哈。 记录下&#xff0c;加深记忆&#xff0c;给同样小白的人…

第54步 深度学习图像识别:MLP-Mixer建模(Pytorch)

基于WIN10的64位系统演示 一、写在前面 &#xff08;1&#xff09;MLP-Mixer MLP-Mixer&#xff08;Multilayer Perceptron Mixer&#xff09;是Google在2021年提出的一种新型的视觉模型结构。它的主要特点是完全使用多层感知机&#xff08;MLP&#xff09;来处理图像&#…

Element-UI下拉多选框回显

1、v-model绑定下拉框 <el-form-item label"项目成员" prop"members"><el-select v-model"form.members" placeholder"请选择项目成员" multiple clearable:style"{width: 100%}" change"userNameSelect"…

单行多行文本溢出显示省略号

在CSS中&#xff0c;你可以使用text-overflow属性来实现文本溢出显示省略号。同时&#xff0c;你还需要结合white-space和overflow属性来控制文本的换行和溢出隐藏。下面分别介绍单行和多行文本溢出显示省略号的方法&#xff1a; 单行文本溢出显示省略号&#xff1a; 对于单行…

【CSS】box-shadow 属性

box-shadow 是 CSS 属性&#xff0c;用于为元素添加一个阴影效果&#xff0c;使元素看起来浮起或有层次感。 该属性允许设置一个或多个阴影效果&#xff0c;其语法如下&#xff1a; box-shadow: h-shadow v-shadow blur spread color inset;h-shadow&#xff1a;水平阴影的位…