JUC并发—9.并发安全集合三
大纲
1.并发安全的数组列表CopyOnWriteArrayList
2.并发安全的链表队列ConcurrentLinkedQueue
3.并发编程中的阻塞队列概述
4.JUC的各种阻塞队列介绍
5.LinkedBlockingQueue的具体实现原理
6.基于两个队列实现的集群同步机制
1.并发安全的数组列表CopyOnWriteArrayList
(1)CopyOnWriteArrayList的初始化
(2)基于锁 + 写时复制机制实现的增删改操作
(3)使用写时复制的原因是读操作不加锁 + 不使用Unsafe读取数组元素
(4)对数组进行迭代时采用了副本快照机制
(5)核心思想是通过弱一致性提升读并发
(6)写时复制的总结
(1)CopyOnWriteArrayList的初始化
并发安全的HashMap是ConcurrentHashMap
并发安全的ArrayList是CopyOnWriteArrayList
并发安全的LinkedList是ConcurrentLinkedQueue
从CopyOnWriteArrayList的构造方法可知,CopyOnWriteArrayList基于Object对象数组实现。
这个Object对象数组array会使用volatile修饰,保证了多线程下的可见性。只要有一个线程修改了数组array,其他线程可以马上读取到最新值。
//A thread-safe variant of java.util.ArrayList in which all mutative operations
//(add, set, and so on) are implemented by making a fresh copy of the underlying array.
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ...//The lock protecting all mutatorsfinal transient ReentrantLock lock = new ReentrantLock();//The array, accessed only via getArray/setArray.private transient volatile Object[] array;//Creates an empty list.public CopyOnWriteArrayList() {setArray(new Object[0]);}//Sets the array.final void setArray(Object[] a) {array = a;}...
}
(2)基于锁 + 写时复制机制实现的增删改操作
一.使用独占锁解决对数组的写写并发问题
每个CopyOnWriteArrayList都有一个Object数组 + 一个ReentrantLock锁。在对Object数组进行增删改时,都要先获取锁,保证只有一个线程增删改。从而确保多线程增删改CopyOnWriteArrayList的Object数组是并发安全的。注意:获取锁的动作需要在执行getArray()方法前执行。
但因为获取独占锁,所以导致CopyOnWriteArrayList的写并发并性能不太好。而ConcurrentHashMap由于通过CAS设置 + 分段加锁,所以写并发性能很高。
二.使用写时复制机制解决对数组的读写并发问题
CopyOnWrite就是写时复制。写数据时不直接在当前数组里写,而是先把当前数组的数据复制到新数组里。然后再在新数组里写数据,写完数据后再将新数组赋值给array变量。这样原数组由于没有了array变量的引用,很快就会被JVM回收掉。
其中会使用System.arraycopy()方法和Arrays.copyOf()方法来复制数据到新数组,从Arrays.copyOf(elements, len + 1)可知,新数组的大小比原数组大小多1。
所以CopyOnWriteArrayList不需要进行数组扩容,这与ArrayList不一样。ArrayList会先初始化一个固定大小的数组,然后数组大小达到阈值时会扩容。
三.总结
为了解决CopyOnWriteArrayList的数组写写并发问题,使用了锁。
为了解决CopyOnWriteArrayList的数组读写并发问题,使用了写时复制。
所以CopyOnWriteArrayList可以保证多线程对数组写写 + 读写的并发安全。
//A thread-safe variant of java.util.ArrayList in which all mutative operations
//(add, set, and so on) are implemented by making a fresh copy of the underlying array.
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ...//The lock protecting all mutatorsfinal transient ReentrantLock lock = new ReentrantLock();//The array, accessed only via getArray/setArray.private transient volatile Object[] array;//Creates an empty list.public CopyOnWriteArrayList() {setArray(new Object[0]);}//Sets the array.final void setArray(Object[] a) {array = a;}//Gets the array. Non-private so as to also be accessible from CopyOnWriteArraySet class.final Object[] getArray() {return array;}//增:Appends the specified element to the end of this list.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();}}//删:Removes the element at the specified position in this list.//Shifts any subsequent elements to the left (subtracts one from their indices). //Returns the element that was removed from the list.public E remove(int index) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;E oldValue = get(elements, index);int numMoved = len - index - 1;if (numMoved == 0) {setArray(Arrays.copyOf(elements, len - 1));} else {//先创建新数组,新数组的大小为len-1,比原数组的大小少1Object[] newElements = new Object[len - 1];//把原数组里从0开始拷贝index个元素到新数组里,并且从新数组的0位置开始放置System.arraycopy(elements, 0, newElements, 0, index);//把原数组从index+1开始拷贝numMoved个元素到新数组里,并且从新数组的index位置开始放置;System.arraycopy(elements, index + 1, newElements, index, numMoved);setArray(newElements);}return oldValue;} finally {lock.unlock();}}//改:Replaces the element at the specified position in this list with the specified element.public E set(int index, E element) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();E oldValue = get(elements, index);if (oldValue != element) {int len = elements.length;Object[] newElements = Arrays.copyOf(elements, len);newElements[index] = element;setArray(newElements);} else {//Not quite a no-op; ensures volatile write semanticssetArray(elements);}return oldValue;} finally {lock.unlock();}}...
}
(3)使用写时复制的原因是读操作不加锁 + 不使用Unsafe读取数组元素
CopyOnWriteArrayList的增删改采用写时复制的原因在于get操作不需加锁。get操作就是先获取array数组,然后再通过index定位返回对应位置的元素。
由于在写数据的时候,首先更新的是复制了原数组数据的新数组。所以同一时间大量的线程读取数组数据时,都会读到原数组的数据,因此读写之间不会出现并发冲突的问题。
而且在写数据的时候,在更新完新数组之后,才会更新volatile修饰的数组变量。所以读操作只需要直接对volatile修饰的数组变量进行读取,就能获取最新的数组值。
如果不使用写时复制机制,那么即便有写线程先更新了array引用的数组中的元素,后续的读线程也只是具有对使用volatile修饰的array引用的可见性,而不会具有对array引用的数组中的元素的可见性。所以此时只要array引用没有发生改变,读线程还是会读到旧的元素,除非使用Unsafe.getObjectVolatile()方法来获取array引用的数组的元素。
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {...//The array, accessed only via getArray/setArray.private transient volatile Object[] array;//Gets the array. Non-private so as to also be accessible from CopyOnWriteArraySet class.final Object[] getArray() {return array;}public E get(int index) {//先通过getArray()方法获取array数组,然后再通过get()方法定位到数组某位置的元素return get(getArray(), index);}private E get(Object[] a, int index) {return (E) a[index];}...
}
(4)对数组进行迭代时采用了副本快照机制
CopyOnWriteArrayList的Iterator迭代器里有一个快照数组snapshot,该数组指向的就是创建迭代器时CopyOnWriteArrayList的当前数组array。
所以使用CopyOnWriteArrayList的迭代器进行迭代时,会遍历快照数组。此时如果有其他线程更新了数组array,也不会影响迭代的过程。
public class CopyOnWriteArrayListDemo {static List<String> list = new CopyOnWriteArrayList<String>();public static void main(String[] args) {list.add("k");System.out.println(list);Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}}
}public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {...public Iterator<E> iterator() {return new COWIterator<E>(getArray(), 0);}...static final class COWIterator<E> implements ListIterator<E> {private final Object[] snapshot;private int cursor;private COWIterator(Object[] elements, int initialCursor) {cursor = initialCursor;snapshot = elements;}...}...
}
(5)核心思想是通过最终一致性提升读并发
CopyOnWriteArrayList的核心思想是通过弱一致性来提升读写并发的能力。
CopyOnWriteArrayList基于写时复制机制存在的最大问题是最终一致性。
多个线程并发读写数组,写线程已将新数组修改好,但还没设置给array。此时其他读线程读到的(get或者迭代)都是数组array的数据,于是在同一时刻,读线程和写线程看到的数据是不一致的。这就是写时复制机制存在的问题:最终一致性或弱一致性。
(6)写时复制的总结
一.优点
读读不互斥,读写不互斥,写写互斥。同一时间只有一个线程可以写,写的同时允许其他线程来读。
二.缺点
空间换时间,写的时候内存里会出现一模一样的副本,对内存消耗大。通过数组副本可以保证大量的读不需要和写互斥。如果数组很大,可能要考虑内存占用会是数组大小的几倍。此外使用数组副本来统计数据,会存在统计数据不一致的问题。
三.使用场景
适用于读多写少的场景,这样大量的读操作不会被写操作影响,而且不要求统计数据具有实时性。
2.并发安全的链表队列ConcurrentLinkedQueue
(1)ConcurrentLinkedQueue的介绍
(2)ConcurrentLinkedQueue的构造方法
(3)ConcurrentLinkedQueue的offer()方法
(4)ConcurrentLinkedQueue的poll()方法
(5)ConcurrentLinkedQueue的peak()方法
(6)ConcurrentLinkedQueue的size()方法
(1)ConcurrentLinkedQueue的介绍
ConcurrentLinkedQueue是一种并发安全且非阻塞的链表队列(无界队列)。
ConcurrentLinkedQueue采用CAS机制来保证多线程操作队列时的并发安全。
链表队列会采用先进先出的规则来对结点进行排序。每次往链表队列添加元素时,都会添加到队列的尾部。每次需要获取元素时,都会直接返回队列头部的元素。
并发安全的HashMap是ConcurrentHashMap
并发安全的ArrayList是CopyOnWriteArrayList
并发安全的LinkedList是ConcurrentLinkedQueue
(2)ConcurrentLinkedQueue的构造方法
ConcurrentLinkedQueue是基于链表实现的,链表结点为其内部类Node。
ConcurrentLinkedQueue的构造方法会初始化链表的头结点和尾结点为同一个值为null的Node对象。
Node结点通过next指针指向下一个Node结点,从而组成一个单向链表。而ConcurrentLinkedQueue的head和tail两个指针指向了链表的头和尾结点。
public class ConcurrentLinkedQueueDemo {public static void main(String[] args) {ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<String>();queue.offer("张三");//向队尾添加元素queue.offer("李四");//向队尾添加元素queue.offer("王五");//向队尾添加元素System.out.println(queue.peek());//返回队头的元素不出队System.out.println(queue.poll());//返回队头的元素而且出队System.out.println(queue.peek());//返回队头的元素不出队}
}//An unbounded thread-safe queue based on linked nodes.
//This queue orders elements FIFO (first-in-first-out).
//The head of the queue is that element that has been on the queue the longest time.
//The tail of the queue is that element that has been on the queue the shortest time.
//New elements are inserted at the tail of the queue,
//and the queue retrieval operations obtain elements at the head of the queue.
//A ConcurrentLinkedQueue is an appropriate choice when many threads will share access to a common collection.
//Like most other concurrent collection implementations, this class does not permit the use of null elements.
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E> implements Queue<E>, java.io.Serializable {...private transient volatile Node<E> head;private transient volatile Node<E> tail;//构造方法,初始化链表队列的头结点和尾结点为同一个值为null的Node对象//Creates a ConcurrentLinkedQueue that is initially empty.public ConcurrentLinkedQueue() {head = tail = new Node<E>(null);}private static class Node<E> {volatile E item;volatile Node<E> next;private static final sun.misc.Unsafe UNSAFE;private static final long itemOffset;private static final long nextOffset;static {try {UNSAFE = sun.misc.Unsafe.getUnsafe();Class<?> k = Node.class;itemOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("item"));nextOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("next"));} catch (Exception e) {throw new Error(e);}}Node(E item) {UNSAFE.putObject(this, itemOffset, item);}boolean casItem(E cmp, E val) {return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);}void lazySetNext(Node<E> val) {UNSAFE.putOrderedObject(this, nextOffset, val);}boolean casNext(Node<E> cmp, Node<E> val) {return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);}}...
}
(3)ConcurrentLinkedQueue的offer()方法
其中关键的代码就是"p.casNext(null, newNode))",就是把p的next指针由原来的指向空设置为指向新的结点,并且通过CAS确保同一时间只有一个线程可以成功执行这个操作。
注意:更新tail指针并不是实时更新的,而是隔一个结点再更新。这样可以减少CAS指令的执行次数,从而降低CAS操作带来的性能影响。
插入第一个元素后,tail指针指向倒数第二个节点。
插入第二个元素后,tail指针指向最后一个节点。
插入第三个元素后,tail指针指向倒数第二个节点。
插入第四个元素后,tail指针指向最后一个节点。
//An unbounded thread-safe queue based on linked nodes.
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E> implements Queue<E>, java.io.Serializable {...private transient volatile Node<E> head;private transient volatile Node<E> tail;private static final sun.misc.Unsafe UNSAFE;private static final long headOffset;private static final long tailOffset;static {try {UNSAFE = sun.misc.Unsafe.getUnsafe();Class<?> k = ConcurrentLinkedQueue.class;headOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("head"));tailOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("tail"));} catch (Exception e) {throw new Error(e);}}//构造方法,初始化链表队列的头结点和尾结点为同一个值为null的Node对象//Creates a ConcurrentLinkedQueue that is initially empty.public ConcurrentLinkedQueue() {head = tail = new Node<E>(null);}public boolean offer(E e) {checkNotNull(e);final Node<E> newNode = new Node<E>(e);//插第一个元素时, tail和head都是初始化时的空节点, p也指向该空节点, q是该空节点的next元素;//很明显q是null, p.casNext后, p的next设为第一个元素, 此时p和t相等, tail的next是第一个元素;//由于p==t, 于是返回true, head和tail还是指向初始化时的空节点, tail指针指向的是倒数第二个节点;//插第二个元素时, q成为第一个元素,不为null了, 而且p指向tail, tail的next是第一个元素, 所以p != q;//由于此时p和t还是一样的, 所以会将q赋值给p, 也就是p指向第一个元素了, 再次进行新一轮循环;//新一轮循环时, q指向第一个元素的next成为null, 所以会对第一个元素执行casNext操作;//也就是将第二个元素设为第一个元素的next, 设完后由于p和t不相等了, 会执行casTail设第二个元素为tail;//插入第三个元素时, 又会和插入第一个元素一样了, 这时tail指针指向的是倒数第二个节点;//插入第四个元素时, 和插入第二个元素一样, 这是tail指针指向的是最后一个节点;for (Node<E> t = tail, p = t;;) {Node<E> q = p.next;//p是尾结点,q是尾结点的下一个结点if (q == null) {//插入第一个元素时执行的代码if (p.casNext(null, newNode)) {//将新结点设置为尾结点的下一个结点if (p != t) {//隔一个结点再CAS更新tail指针casTail(t, newNode);}return true;}} else if (p == q) {p = (t != (t = tail)) ? t : head;} else {//插入第二个元素时执行的代码p = (p != t && t != (t = tail)) ? t : q;}}}private boolean casTail(Node<E> cmp, Node<E> val) {return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val);}...
}
(4)ConcurrentLinkedQueue的poll()方法
poll()方法会将链表队列的头结点出队。
注意:更新head指针时也不是实时更新的,而是隔一个结点再更新。这样可以减少CAS指令的执行次数,从而降低CAS操作带来的性能影响。
//An unbounded thread-safe queue based on linked nodes.
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E> implements Queue<E>, java.io.Serializable {...private transient volatile Node<E> head;private transient volatile Node<E> tail;private static final sun.misc.Unsafe UNSAFE;private static final long headOffset;private static final long tailOffset;static {try {UNSAFE = sun.misc.Unsafe.getUnsafe();Class<?> k = ConcurrentLinkedQueue.class;headOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("head"));tailOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("tail"));} catch (Exception e) {throw new Error(e);}}//构造方法,初始化链表队列的头结点和尾结点为同一个值为null的Node对象//Creates a ConcurrentLinkedQueue that is initially empty.public ConcurrentLinkedQueue() {head = tail = new Node<E>(null);}public E poll() {restartFromHead:for (;;) {for (Node<E> h = head, p = h, q;;) {E item = p.item;if (item != null && p.casItem(item, null)) {if (p != h) {//隔一个结点才CAS更新head指针updateHead(h, ((q = p.next) != null) ? q : p);}return item;} else if ((q = p.next) == null) {updateHead(h, p);return null;} else if (p == q) {continue restartFromHead;} else {p = q;}}}}final void updateHead(Node<E> h, Node<E> p) {if (h != p && casHead(h, p)) {h.lazySetNext(h);}}private boolean casHead(Node<E> cmp, Node<E> val) {return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val);}...
}
(5)ConcurrentLinkedQueue的peak()方法
peek()方法会获取链表的头结点,但是不会出队。
//An unbounded thread-safe queue based on linked nodes.
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E> implements Queue<E>, java.io.Serializable {...private transient volatile Node<E> head;public E peek() {restartFromHead:for (;;) {for (Node<E> h = head, p = h, q;;) {E item = p.item;if (item != null || (q = p.next) == null) {updateHead(h, p);return item;} else if (p == q) {continue restartFromHead;} else {p = q;}}}}final void updateHead(Node<E> h, Node<E> p) {if (h != p && casHead(h, p)) {h.lazySetNext(h);}}private boolean casHead(Node<E> cmp, Node<E> val) {return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val);}...
}
(6)ConcurrentLinkedQueue的size()方法
size()方法主要用来返回链表队列的大小,查看链表队列有多少个元素。size()方法不会加锁,会直接从头节点开始遍历链表队列中的每个结点。
//An unbounded thread-safe queue based on linked nodes.
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E> implements Queue<E>, java.io.Serializable {...public int size() {int count = 0;for (Node<E> p = first(); p != null; p = succ(p))if (p.item != null) {if (++count == Integer.MAX_VALUE) {break;}}}return count;}//Returns the first live (non-deleted) node on list, or null if none.//This is yet another variant of poll/peek; here returning the first node, not element.//We could make peek() a wrapper around first(), but that would cost an extra volatile read of item,//and the need to add a retry loop to deal with the possibility of losing a race to a concurrent poll(). Node<E> first() {restartFromHead:for (;;) {for (Node<E> h = head, p = h, q;;) {boolean hasItem = (p.item != null);if (hasItem || (q = p.next) == null) {updateHead(h, p);return hasItem ? p : null;} else if (p == q) {continue restartFromHead;} else {p = q;}}}}//Returns the successor of p, or the head node if p.next has been linked to self, //which will only be true if traversing with a stale pointer that is now off the list.final Node<E> succ(Node<E> p) {Node<E> next = p.next;return (p == next) ? head : next;}...
}
如果在遍历的过程中,有线程执行入队或者是出队的操作,此时会怎样?
从队头开始遍历,遍历到一半时:如果有线程在队列尾部进行入队操作,此时的遍历能及时看到新添加的元素。因为入队操作就是设置队列尾部节点的next指针指向新添加的结点,而入队时设置next指针属于volatile写,因此遍历时是可以看到的。如果有线程从队列头部进行出队操作,此时的遍历则无法感知有元素出队了。
所以可以总结出这些并发安全的集合:ConcurrentHashMap、CopyOnWriteArrayList和ConcurrentLinkedQueue,为了优化多线程下的并发性能,会牺牲掉统计数据的一致性。为了保证多线程写的高并发性能,会大量采用CAS进行无锁化操作。同时会让很多读操作比如常见的size()操作,不使用锁。因此使用这些并发安全的集合时,要考虑并发下的统计数据的不一致问题。
3.并发编程中的阻塞队列概述
(1)什么是阻塞队列
(2)阻塞队列提供的方法
(3)阻塞队列的应用
(1)什么是阻塞队列
队列是一种只允许在一端进行移除操作、在另一端进行插入操作的线性表,队列中允许插入的一端称为队尾,允许移除的一端称为队头。
阻塞队列就是在队列的基础上增加了两个操作:
一.支持阻塞插入
在队列满时会阻塞继续往队列中添加数据的线程,直到队列中有元素被释放。
二.支持阻塞移除
在队列空时会阻塞从队列中获取元素的线程,直到队列中添加了新的元素。
阻塞队列其实实现了一个生产者/消费者模型:生产者往队列中添加数据,消费者从队列中获取数据。队列满了阻塞生产者,队列空了阻塞消费者。
阻塞队列中的元素可能会使用数组或者链表等来进行存储。一个队列中能容纳多少个元素取决于队列的容量大小,因此阻塞队列也分为有界队列和无界队列。
有界队列指有固定大小的队列,无界队列指没有固定大小的队列。实际上无界队列也是有大小限制的,只是大小限制为非常大,可认为无界。
注意:在无界队列中,由于理论上不存在队列满的情况,所以不存在阻塞。
阻塞队列在很多地方都会用到,比如线程池、ZooKeeper。一般使用阻塞队列来实现生产者/消费者模型。
(2)阻塞队列提供的方法
阻塞队列的操作有插入、移除、检查,在队列满或者空时会有不同的效果。
一.抛出异常
当队列满的时候通过add(e)方法添加元素,会抛出异常。
当队列空的时候调用remove(e)方法移除元素,也会抛出异常。
二.返回特殊值
调用offer(e)方法向队列入队元素时,会返回添加结果true或false。
调用poll()方法从队列出队元素时,会从队列取出一个元素或null。
三.一直阻塞
在队列满了的情况下,调用插入方法put(e)向队列中插入元素时,队列会阻塞插入元素的线程,直到队列不满或者响应中断才退出阻塞。
在队列空了的情况下,调用移除方法take()从队列移除元素时,队列会阻塞移除元素的线程,直到队列不为空时唤醒线程。
四.超时退出
超时退出其实就是在offer()和poll()方法中增加了阻塞的等待时间。
(3)阻塞队列的应用
阻塞队列可以理解为线程级别的消息队列。
消息中间件可以理解为进程级别的消息队列。
所以可以通过阻塞队列来缓存线程的请求,从而达到流量削峰的目的。
相关文章:
JUC并发—9.并发安全集合三
大纲 1.并发安全的数组列表CopyOnWriteArrayList 2.并发安全的链表队列ConcurrentLinkedQueue 3.并发编程中的阻塞队列概述 4.JUC的各种阻塞队列介绍 5.LinkedBlockingQueue的具体实现原理 6.基于两个队列实现的集群同步机制 1.并发安全的数组列表CopyOnWriteArrayList …...
后端Java Stream数据流的使用=>代替for循环
API讲解 对比 示例代码对比 for循环遍历 package cn.ryanfan.platformback.service.impl;import cn.ryanfan.platformback.entity.Algorithm; import cn.ryanfan.platformback.entity.AlgorithmCategory; import cn.ryanfan.platformback.entity.DTO.AlgorithmInfoDTO; im…...
强化学习-GAE方法
2016-ICLR-HIGH-DIMENSIONAL CONTINUOUS CONTROL USING GENERALIZED ADVANTAGE ESTIMATION 解决问题 强化学习的目标为最大化策略的预期总回报,其中一个主要困难为 行为对reward的影响存在一个长时间的延迟(credit assignment problem)。价…...
51c大模型~合集71
我自己的原文哦~ https://blog.51cto.com/whaosoft/12260659 #大模型推理加速技术的学习路线 EfficientQAT 可以在 41 小时内在单个 A100-80GB GPU 上完成对 2-bit Llama-2-70B 模型的量化感知训练。与全精度模型相比,精度仅下降了不到 3%(69.48 v…...
PyTorch-基础(CUDA、Dataset、transforms、卷积神经网络、VGG16)
PyTorch-基础 环境准备 CUDA Toolkit安装(核显跳过此步骤) CUDA Toolkit是NVIDIA的开发工具,里面提供了各种工具、如编译器、调试器和库 首先通过NVIDIA控制面板查看本机显卡驱动对应的CUDA版本,如何去下载对应版本的Toolkit工…...
Linux(centos)系统安装部署MySQL8.0数据库(GLIBC版本)
安装前检查服务器glibc版本,下载对应版本包 rpm -qa | grep glibc mysql安装包及依赖包已整理好,下载地址:https://pan.quark.cn/s/3137acc814c0,下载即可安装 一、下载MySQL mysql安装包及依赖包已整理好,下载地址…...
Transformer解析——(四)Decoder
本系列已完结,全部文章地址为: Transformer解析——(一)概述-CSDN博客 Transformer解析——(二)Attention注意力机制-CSDN博客 Transformer解析——(三)Encoder-CSDN博客 Transforme…...
TypeScript 中 type 和 interface 深度对比分析
一、核心概念对比 特性interfacetype本质声明式类型契约(面向对象范式)类型别名(函数式编程思维)可扩展性支持声明合并(多次声明自动合并)不可合并基础类型支持只能描述对象/函数类型可描述任意类型&#…...
unplugin-vue-components,unplugin-auto-import,借用ai去学习
好的,我现在需要帮助用户理解他们提供的这段Vite配置代码。用户之前已经询问过关于ElementPlusResolver的导入问题,现在他们展示了在vite.config.ts中使用该解析器的具体配置。我需要逐步分析这段代码,并解释每个部分的作用。 首先ÿ…...
Nginx学习笔记:常用命令端口占用报错解决Nginx核心配置文件解读
Nginx 1. 基础命令1.1 重新加载systemd配置1.2 停止Nginx服务1.3 启动Nginx服务1.4 重启Nginx服务1.5 查看Nginx服务状态1.6 测试配置和重载Nginx 2. 额外命令2.1 启用开机自启2.2 禁用开机自启2.3 强制关闭所有Nginx进程 3. Nginx端口占用解决方案3.1 查找占用端口8090的进程3…...
C++ ——继承
体现的是代码复用的思想 1、子类继承父类,子类就拥有了父类的特性(成员方法和成员属性) 2、已存在的类被称为“基类”或者“父类”或者“超类”;新创建的类被称为“派生类”或者“子类” 注意: (1&#…...
正则表达式常用记录
1. 定义 正则表达式,又称规则表达式,(Regular Expression,在代码中常简写为regex、regexp或RE),它是一种文本模式,同时也是计算机科学的一个概念,其中包括普通字符(例如,…...
redis的应用,缓存,分布式锁
1.应用 1.1可以用作缓存 作用:提交数据的查询效率,减少对数据库的访问频率 什么数据适合放入缓存 1.查询频率高,修改频率低 2.对安全系数比较低 如何实现 Service public class DeptServer {Autowiredprivate DeptMapper deptMapper;Auto…...
qt5实现表盘的旋转效果,通过提升QLabel类
因为工作需要,需要实现温度的表盘展示效果 实现思路: 通过提示声QLabel控价类,实现报盘的旋转和展示效果 1. 编写一个QLabel的类MyQLabel,实现两个方法 1. void paintEvent(QPaintEvent *event); //重绘函数 2. void valueChanged(int va…...
Flutter项目中设置安卓启动页
AndroidManifest.xml 设置 android:theme“style/LaunchTheme” <applicationandroid:label"string/app_name"android:name"${applicationName}"android:icon"mipmap/ic_launcher"android:roundIcon"mipmap/ic_launcher"android:t…...
人工智能之目标追踪DeepSort源码解读(yolov5目标检测,代价矩阵,余弦相似度,马氏距离,匹配与预测更新)
要想做好目标追踪,须做好目标检测,所以这里就是基于yolov5检测基础上进行DeepSort,叫它为Yolov5_DeepSort。整体思路是先检测再追踪,基于检测结果进行预测与匹配。 一.参数与演示 这里用到的是coco预训练人的数据集: 二.针对检测结果初始化track 对每一帧数据都输出…...
C语言之枚举类型
目录 前言 一、enum(枚举 总结 前言 在C语言中,枚举类型是一种用户自定义的数据类型,用于定义一组具名的常量集合。枚举类型可以提高代码的可读性和可维护性,同时也能够帮助程序员避免使用魔法数字。通过枚举类型,我们…...
【Python爬虫(12)】正则表达式:Python爬虫的进阶利刃
【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取ÿ…...
推荐一款AI大模型托管平台-OpenWebUI
推荐一款AI大模型托管平台-OpenWebUI 1. OpenWebUI 1. OpenWebUI什么? 官网地址:https://openwebui.com/ GitHub地址: https://github.com/open-webui/open-webui Open WebUI 是一个可扩展、功能丰富且用户友好的自托管 AI 平台,旨在完全离…...
复习dddddddd
1. 思路:用队列先进先出的特性 #include <iostream> #include <vector> #include <stack> #include <cstdio> #include <algorithm> #include <cstring> #include <climits> #include <cstdlib> #include <cma…...
【3.5JavaScript】JavaScript字符串对象
文章目录 1.获取字符串长度2.大小写转换3.获取某一个字符4.截取字符串5.替换字符串6.分割字符串7.检索字符串位置8.例题:统计某一个字符的个数 在 JavaScript 中,对象是非常重要的知识点。对象分为两种:一种是 ”自定义对象“,另…...
消息队列-持续更新中
消息队列 0、消息队列官方参考文档 MQ官方参考文档 RocketMQ 官方文档: https://rocketmq.apache.org/docs/quick-start/ RocketMQ 中国开发者中心:http://rocketmq.cloud/zh-cn/ Kafka 官方文档: http://kafka.apache.org/documentation/ …...
创建一个简单的spring boot+vue前后端分离项目
一、环境准备 此次实验需要的环境: jdk、maven、nvm和node.js 开发工具:idea或者Spring Tool Suite 4,前端可使用HBuilder X,数据库Mysql 下面提供maven安装与配置步骤和nvm安装与配置步骤: 1、maven安装与配置 1…...
已知点矩阵的三个顶点坐标、行列数和行列的间距,计算得出剩余所有点的坐标
已知点矩阵的三个顶点坐标、行列数和行列的间距,计算得出剩余所有点的坐标 计算矩阵中每个点的坐标代码实现案例图调用验证 计算矩阵中每个点的坐标 给定左上角、左下角和右上角三个点的坐标,以及矩阵的行数、列数、行间距和列间距,我们可以…...
视频mp4垂直拼接 水平拼接
视频mp4垂直拼接 水平拼接 pinjie_v.py import imageio import numpy as np import os import cv2def pinjie_v(dir1,dir2,out_dir):os.makedirs(out_dir, exist_okTrue)# 获取目录下的所有视频文件video_files_1 [f for f in os.listdir(dir1) if f.endswith(.mp4)]video_fi…...
【记录54】渐变色 linear-gradient / radial-gradient
linear-gradient 线性渐变:是以直线条渐变 radial-gradient 径向渐变:是以图型形状渐变 <!-- 线性渐变(从一个方向到另一个方向 --><div style" background: linear-gradient(to right, red, blue);"></div><…...
一周学会Flask3 Python Web开发-response响应格式
锋哥原创的Flask3 Python Web开发 Flask3视频教程: 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 在HTTP响应中,数据可以通过多种格式传输。大多数情况下,我们会使用HTML格式,这也是Flask中…...
二级公共基础之数据结构与算法篇(八)排序技术
目录 前言 一、交换类排序 1.冒泡排序法 1. 冒泡排序的思想 2. 冒泡排序的实现步骤 3. 示例 4. 冒泡排序的特点 2.快速排序 1. 快速排序的核心思想 2. 快速排序的实现步骤 3. 示例代码(C语言) 4. 快速排序的特点 二、插入类排序 1. 简单插入排序 1.简单插入排…...
以ChatGPT为例解析大模型背后的技术
目录 1、大模型分类 2、为什么自然语言处理可计算? 2.1、One-hot分类编码(传统词表示方法) 2.2、词向量 3、Transformer架构 3.1、何为注意力机制? 3.2、注意力机制在 Transformer 模型中有何意义? 3.3、位置编…...
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_cpuinfo 函数
ngx_cpuinfo 声明在 src/core/ngx_core.h void ngx_cpuinfo(void); 定义在 src/core/ngx_cpuinfo.c 这里 ngx_cpuinfo 的定义可以找到 2 个 使用 gcc -E 处理一下来确认当下环境中使用的是哪一个 gcc -E src/core/ngx_cpuinfo.c \-I src/core \-I src/event \-I src/event/modu…...
python小项目编程-中级(1、图像处理)
目录 图像处理 实现 测试 unittest pytest 图像处理 实现界面化操作,使用PIL库实现简单的图像处理功能,如缩放(设置缩放比例)、旋转和滤镜、对比度调整、亮度调整、灰度图、二值化图(二值图如果使用的是彩色图片需…...
EasyExcel实现excel导入(模版上传)
目录 效果pom.xmlapplication.ymlcontrollerservice依赖类前台vue代码某个功能如果需要添加大量的数据,通过一条条的方式添加的方式,肯定不合理,本文通过excel导入的方式来实现该功能,100条数据导入成功85条,失败15条,肯定需要返回一个表格给前台或者返回1个错误excel给前…...
AI工作流+专业知识库+系统API的全流程任务自动化
我有点悲观,甚至很沮丧,因为AI留给普通人的机会不多了,这既是人类之间权力的斗争,也是硅基生命和碳基生命的斗争。AI自动化是无法避免的趋势,如果人类不能平权,那就只能跪下接受审判。 通过整合AI工作流、专…...
【C/C++】合并两个有序链表 (leetcode T21)
核心考点预览:链表 (双指针) 技巧:虚拟头结点 题目描述: 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例: 输入输出示例1l1 [1,2,4], l2 [1…...
C语言进阶习题【2】(4结构体进阶)——通讯录的实现3
1. 本节在动态版本通讯录的基础上实现存储功能 在动态版本的基础上,对于通讯录的新增了存储到文件中,可以从文件中打开我们存储的通信录功能。新增函数:saveContatc()和loadContact() 2. 具体实现 2.1 contact.h /…...
Linux系统编程之无名管道
概述 在Linux系统中,无名管道是一种简单的进程间通信机制。它允许一个进程创建一对文件描述符,其中一个用于读取,另一个用于写入。当一个进程通过系统调用创建了一个无名管道后,便可以将这两个文件描述符传递给它的子进程…...
deepseek与其他大模型配合组合
DeepSeek与其他大模型的配合组合,展现了其在多个领域中的强大应用潜力和灵活性。以下是对DeepSeek与其他大模型配合组合的详细分析: 一、DeepSeek与华知大模型的组合 背景介绍: 华知大模型是同方知网与华为联手打造的,具备全学科…...
ASP.NET Core Clean Architecture
文章目录 项目地址一、1. 重点1.1 Repository数据库接口1.2 GetEventDetail 完整的Query流程1.3 创建Command并使用validation 项目地址 教程作者:ASP.NET Core Clean Architecture 2022-12 教程地址: https://www.bilibili.com/video/BV1YZ421M7UA?…...
DeepSeek安装部署笔记(一)
Ollamaopen-WebUI部署 DeepSeek安装部署笔记第一步 Ollama安装1.安装ollama:官网https://ollama.com/下载2.上面安装完成,在cmd命令行: 第二步 给DeepSeek添加OpenWebUI界面(重点)1.安装conda:用它来管理py…...
ProfiNet转EtherNet/IP罗克韦尔PLC与监控系统通讯案例
一、案例背景 在新能源产业蓬勃发展的当下,大型光伏电站作为绿色能源的重要输出地,其稳定高效的运行至关重要。某大型光伏电站占地面积广阔,内部设备众多,要保障电站的稳定运行,对站内各类设备进行集中监控与管理必不可…...
23.2 HtmlDocument类
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。 HtmlDocument类提供了HTML文档的顶级编程访问,配合WebBrowser的 Document属性使用,可以获得WebBrowser当前页…...
wordpress adrotate插件 文件上传漏洞
当你爆破进wordpress后台但权限不是管理员的时,如果你有adrotate插件操作权限可以用adrotate的文件上传功能get webshell 该漏洞需要AdRotate版本 < 5.13.3 第一步按顺序点击上传文件 在这里文件一定要压缩成zip格式,上传的时候也是上传这个zip 上…...
数据分析和数据挖掘的工作内容
基本的数据分析工作通常包含以下几个方面的内容: 确定目标(输入):理解业务,确定指标口径。获取数据:数据仓库(SQL提数)、电子表格、三方接口、网络爬虫、开放数据集等。清洗数据&am…...
【Pandas】pandas Series sample
Pandas2.2 Series Computations descriptive stats 方法描述Series.align(other[, join, axis, level, …])用于将两个 Series 对齐,使其具有相同的索引Series.case_when(caselist)用于根据条件列表对 Series 中的元素进行条件判断并返回相应的值Series.drop([lab…...
qt + opengl 给立方体增加阴影
在前几篇文章里面学会了通过opengl实现一个立方体,那么这篇我们来学习光照。 风氏光照模型的主要结构由3个分量组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。下面这张图展示了这些光照分量看起来的样子: 1 环境光照(Ambient …...
buuctf-[极客大挑战 2019]Knife题解
一个很简单的web题,进入界面 网页名还加白给的shell,并且给的提示也很明显,给了一个一句话木马再加上菜刀,很怀疑是一个webshell题,那么直接打开蚁剑测试连接拿shell 用提示的一句话木马的密码,测试链接发现…...
常用电脑,护眼软件推荐 f.lux 3400K | 撰写论文 paper
常用电脑?平均每天用 5 个小时?你就要考虑用一个护眼软件了,对皮肤也好。因为电脑屏幕有辐射,比如蓝光。 f.lux 作为一款专业护眼软件,值得使用。之前用了三年的 Iris Pro,现在 f.lux 做的更好了。 使用…...
【操作幂等和数据一致性】保障业务在MySQL和COS对象存储的一致
业务场景 发布信息,更新到数据库MySQLCOS操作,更新JSON文件 不过可能存在幂等性和数据一致性的问题。 // 批量存MySQL entityPublishService.saveOrUpdateBatch(entityPublishList); // 遍历批量存COS对象存储searchEntitys.forEach(req -> {//删除…...
[答疑]领域建模:邓丽君、周杰伦和少女时代
DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 第五元素 2025-2-18 17:12 潘老师,画线的这句话,在这个类图中怎么体现呢? (回答者补注:问题的素材来自《邓丽君的领域建模》…...
【鸿蒙开发】第四十三章 Notification Kit(用户通知服务)
目录 1 简介 1.1 使用场景 1.2 能力范围 1.3 业务流程 1.4 通知样式 1.5 约束限制 1.6 与相关Kit的关系 2 请求通知授权 2.1 接口说明 2.2 开发步骤 3 管理通知角标 3.1 接口说明 3.2 开发步骤 4 管理通知渠道 4.1 通知渠道类型说明 4.2 接口说明…...