【Java 集合】LinkedBlockingDeque
在开始介绍 LinkedBlockingDeque 之前, 我们先看一下 LinkedBlockingDeque 的类图:
从其中可以看出他直接实现了 BlockingDeque 接口, 而 BlockingDeque 又实现了 BlockingQueue 的接口, 所以它本身具备了队列的特性。
而实现 BlockingDeque 使其在 BlockingQueue 的基础上多了 Deque 的功能。
什么是 Deque 呢?
Deque(双端队列)是一种具有两端插入和删除操作的数据结构, 允许在队列的前端和后端执行高效的操作。
Deque 是“双端队列”(Double-Ended Queue)的缩写, 其特点在于可以在两端同时进行元素的插入和删除操作, 这使得它在许多应用中具有卓越的灵活性和性能。
我们知道队列只能是头进尾出, 而 Deque 2 边都支持进出, 这个特性更加的灵活。
实现了 BlockingDeque 的 LinkedBlockingDeque 其本身具备了以下特点
- 不支持 null 元素: 存入 null 元素会抛出异常
- 线程安全性: 在内部中通过 ReentrantLock 保证多线程环境下的安全访问
- 无界容量: 在不指定容量的情况下, 默认为 Integer.MAX_VALUE, 理论上的无限容量
- 阻塞操作: 当 LinkedBlockingDeque 的操作尝试在空队列中执行移除操作或在满队列中执行插入操作时, 它会导致操作的阻塞
1 实现的数据结构
内部的实现结构就是一个链表, 但是为了支持 2 端都可以出入队操作, 所以是一个双向链表。
2 源码分析
2.1 LinkedBlockingDeque 链表节点的定义
我们知道 LinkedBlockingDeque 的底层实现结构就是一个链表, 而链表绕不开的一个概念就是节点, 所以我们先来看一下 LinkedBlockingDeque 的节点定义。
public class LinkedBlockingDeque<E> {/*** 节点类, 用于存储数据*/static class Node<E> {// 存储的内容E item;// 上一个节点的指针Node<E> prev;// 下一个节点的指针Node<E> next;Node(E x) {item = x;}}
}
上面的 Node 就是队列中的节点了, 声明的属性
item: 存储的内容
prev: 上一个节点的指针
next: 下一个节点指针
可以看出 LinkedBlockingDeque 是一个具备双向操作的链表
2.2 LinkedBlockingQueue 持有的属性
public class LinkedBlockingDeque<E> {/** 队列的第一个元素的节点, 也就是头节点*/transient Node<E> first;/** 队列的最后一个元素节点, 也就是尾节点 */transient Node<E> last;/** 当前队列中的元素个数 */private transient int count;/** 队列的大小, 默认为 Integer.MAX_VALUE */private final int capacity;/** 所有操作队列元素节点时使用的锁 */final ReentrantLock lock = new ReentrantLock();// 非空, 也就是数组中重新有数据了, 可以继续取数据了// 当某个线程尝试从当前的队列中获取元素时, 如果数组中没有数据, 会把这个线程放到这个等待条件中// 在另外一个线程中添加元素到数组中, 会唤醒阻塞在这个等待条件中的线程private final Condition notEmpty = lock.newCondition();// 非满, 也就是数组中的数据又没有达到上限了, 可以继续添加数据// 当某个线程尝试向当前的队列添加元素, 但是当前数组已经满了, 会把这个线程放到这个等待条件中// 在另一个线程中从当前队列中获取一个元素时, 会唤醒等待在这个等待队列中的线程private final Condition notFull = lock.newCondition();}
通过上面属性的声明, 可以发现 LinkedBlockingDeque 和 ArrayBlockingQueue 类似, 都是通过一个可重入锁控制并发, 同时通过 2 个 Condition 实现在队列空或满时挂起或唤醒线程。
再来看表示队列容量的 count, 就是简单的用一个 int 表示, 没有用 volatile 修饰, 可以保证线程的可见性吗?
在 LinkedBlockingQueue 容量的大小却是的用 AtomicInteger 来表示, 为什么不是同样简单用一个 int 呢?
这 2 个问题都可以用 Happens-Before 的监视器原则来解释。
在 LinkedBlockingDeque 中只用了一把锁, 同时 count 的修改也只有在线程持有锁的过程中变更, 所以能够保证可见性, 而监视器原则,
确保了同一把锁的解锁 Happens-Before 加锁, 也就是说在 A 线程解锁之前的操作对于 B 线程获取同一把锁后都是可见的。
而 LinkedBlockingQueue 中使用了 2 把锁, 一个用于添加元素, 一个用于移除元素, 所以 2 把锁之间的加锁和解锁不能保证可见性, 所以使用了 AtomicInteger, 利用其内部的 CAS 操作来保证可见性。
2.3 LinkedBlockingQueue 构造函数
public class LinkedBlockingDeque<E> {// 无参构造函数public LinkedBlockingDeque() {// 调用自身指定容量的构造函数, 默认为 int 的最大值this(Integer.MAX_VALUE);}// 指定容量的构造函数public LinkedBlockingDeque(int capacity) {// 指定的容量小于等于 0, 直接抛出异常if (capacity <= 0)throw new IllegalArgumentException();this.capacity = capacity;}// 指定一个集合的构造函数public LinkedBlockingDeque(Collection<? extends E> c) {this(Integer.MAX_VALUE);final ReentrantLock lock = this.lock;// 加锁lock.lock();try {for (E e : c) {// 元素不能为空if (e == null)throw new NullPointerException();// 把当前的数据封装为 Node 节点, 放到了队列的尾部, 添加失败就抛出异常if (!linkLast(new Node<E>(e)))throw new IllegalStateException("Deque full");}} finally {lock.unlock();}}private boolean linkLast(Node<E> node) {// 队列中的元素个数大于等于容量上限了, 直接返回 falseif (count >= capacity)return false;// 获取尾节点Node<E> l = last;// 新增节点的上一个节点为原本的尾节点node.prev = l;// 当前的尾节点等于新增的节点last = node;// 头节点为空, 原本链表没有数据, 新增节点就是头节点if (first == null)first = node;else// 头节点不为空, 原本链表有数据, 那么把原本的尾节点的下一个节点指向新增的节点l.next = node;// 队列中的元素个数 + 1++count;// 队列有数据了, 唤醒阻塞在非空等待条件上的线程notEmpty.signal();return true;}
}
第一个构造函数无参, 那么创建的队列的容量就是 Integer.MAX_VALUE, 也就是理论上的无限容量。
第二个构造函数可以指定队列大小。
第三个构造函数传入一个集合, 这样也会创建出理论无限容量的队列。
同理里面使用了 ReentrantLock 来加锁, 然后把传入的集合元素按顺序一个个放入 items 中。
这里加锁目的不是使用它的互斥性, 而是让修改后的头节点 first 和尾节点 last 的变动, 对其他线程可见, 也就是使用了 ReentrantLock 的监视器原则。
2.4 LinkedBlockingDeque 支持的方法
2.4.1 数据入队方法
LinkedBlockingDeque 提供了多种入队操作的实现来满足不同情况下的需求, 入队操作有如下几种:
- add(E e) / addFirst(E e) / addLast(E e)
- offer(E e) / offerFirst(E e) / offerLast(E e)
- offer(E e, long timeout, TimeUnit unit) / offerFirst(E e, long timeout, TimeUnit unit) / offerLast(E e, long timeout, TimeUnit unit)
- put(E e) / putFirst(E e) / putLast(E e)
add() 相关的方法
public class LinkedBlockingDeque<E> {public boolean add(E e) {// 调用到添加到尾部的方法addLast(e);return true;}public void addFirst(E e) {// 调用 offerFirst 的方法if (!offerFirst(e))throw new IllegalStateException("Deque full");}public void addLast(E e) {// 调用 offerLast 的方法if (!offerLast(e))throw new IllegalStateException("Deque full");}}
add() 相关的方法都是直接调用 offer() 对应的方法, 比如 add 对应 offer, addFirst 对应 offerFirst, 所以我们只需要看 offer() 方法的实现就可以了。
offer() 相关的方法
public class LinkedBlockingDeque<E> {public boolean offer(E e) {// 调用自身的 offerLast 方法, 添加到队列的尾部return offerLast(e);}public boolean offerFirst(E e) {// 添加的数据不能为空if (e == null) throw new NullPointerException();// 封装为 Node 节点 Node<E> node = new Node<E>(e);final ReentrantLock lock = this.lock;// 获取锁lock.lock();try {// 添加到队列的头部return linkFirst(node);} finally {lock.unlock();}}public boolean offerLast(E e) {// 添加的数据不能为空if (e == null) throw new NullPointerException();// 封装为 Node 节点 Node<E> node = new Node<E>(e);final ReentrantLock lock = this.lock;// 获取锁lock.lock();try {// 添加到队列的尾部return linkLast(node);} finally {lock.unlock();}}private boolean linkFirst(Node<E> node) {// 节点个数大于等于容量上限了, 直接返回if (count >= capacity)return false;// 获取头节点 Node<E> f = first;// 新增的节点的下一个节点为当前的头节点node.next = f;// 当前的头节的设置为新增的节点first = node;// 当前的尾节点为空, 表示原本链表中没有数据, 把新增的节点同时设置为尾节点if (last == null)last = node;else// 为节点不为空, 表示原本链表中有数据了// 那么只需要把 旧的头节点的上一个节点指向新增的节点f.prev = node;// 队列中的元素个数 + 1 ++count;// 唤醒阻塞在非空等待条件中的线程notEmpty.signal();return true;}private boolean linkLast(Node<E> node) {if (count >= capacity)return false;// 获取尾结点 Node<E> l = last;// 新增节点的上一个节点为原本的尾节点node.prev = l;// 当前的尾节点等于新增的节点last = node;// 头节点为空, 原本链表没有数据if (first == null)// 将头节点设置为新增的节点first = node;else// 尾节点不为空, 原本队列有数据了, 把原本尾节点的下一个节点设置为新增的节点l.next = node;// 队列中的元素个数 + 1 ++count;// 唤醒阻塞在非空等待条件中的线程notEmpty.signal();return true;}
}
offer() 相关的方法的实现很简单, 最终就是调用到内部的 linkFirst() 和 linkLast() 方法, 而 2 个就是链表的节点新增操作。
而 offer() 相关的带超时时间的方法就不展开了, 原理和 LinkedBlockingQueue 一样, 利用了 Condition 的 awaitNanos 进行超时等待, 并在外面用 while 循环控制等待时的中断问题。
put() 相关的方法
public class LinkedBlockingDeque<E> {public void put(E e) throws InterruptedException {// 调用到 putLast 方法putLast(e);}public void putLast(E e) throws InterruptedException {// 添加的数据不能为空if (e == null) throw new NullPointerException();// 将当前的数据封装为 Node 节点 Node<E> node = new Node<E>(e);final ReentrantLock lock = this.lock;lock.lock();try {// 阻塞等待 linkLast 成功while (!linkLast(node))// 添加到队列尾部失败的话, 将当前线程放到非满的等待条件, 等待唤醒后, 尝试添加元素到队列notFull.await();} finally {lock.unlock();}}public void putFirst(E e) throws InterruptedException {if (e == null) throw new NullPointerException();Node<E> node = new Node<E>(e);final ReentrantLock lock = this.lock;lock.lock();try {// 阻塞等待linkFirst成功while (!linkFirst(node))// 添加到队列尾部失败的话, 当前线程放到非满的等待条件, 等待唤醒后, 尝试添加元素到队列notFull.await();} finally {lock.unlock();}}
}
lock 加锁后一直阻塞等待, 直到元素插入到队列中。
整体的方法和 offer 方法类似, 前者在当队列已满时, 进入阻塞, 后者在队列已满时, 则是返回。
2.3.2 数据出队方法
同入队的方法一样, 出队也有多种实现, LinkedBlockingDeque 提供了好几种出队的方法, 大体如下:
- remove() / removeFirst() /removeLast()
- poll() / pollFirst() / pollLast()
- poll(long timeout, TimeUnit unit) / pollFirst(long timeout, TimeUnit unit) / pollLast(long timeout, TimeUnit unit)
- take() / takeFirst() / takeLast()
remove() 相关的方法
public class LinkedBlockingDeque<E> {public E remove() {// 调用自身的 removeFirst 方法return removeFirst();}public E removeFirst() {// 调用自身的 pollFirst 方法E x = pollFirst();if (x == null) throw new NoSuchElementException();return x;}public E removeLast() {// 调用自身的 pollLast 方法E x = pollLast();if (x == null) throw new NoSuchElementException();return x;}
}
remove() 相关的方法都是调用到对应的 poll() 方法, 在拿到方法的返回值后, 做一层非空的判断。
poll() 相关的方法
public class LinkedBlockingDeque<E> {public E poll() {// 调用到自身的 pollFirst 方法return pollFirst();}public E pollFirst() {final ReentrantLock lock = this.lock;// 加锁lock.lock();try {// 带锁的执行 unlinkFirst 方法return unlinkFirst();} finally {lock.unlock();}}public E pollLast() {final ReentrantLock lock = this.lock;lock.lock();try {// 带锁的执行 unlinkLast 方法return unlinkLast();} finally {lock.unlock();}}private E unlinkFirst() {Node<E> f = first;// 判断当前的头节点, 为空的话, 直接返回 nullif (f == null)return null;// 获取头节点的下一个节点 Node<E> n = f.next;// 获取头节点的数据E item = f.item;// 置空头节点的数据f.item = null;// 把头节点的下一个节点指向自身f.next = f;// 把头节点的指针 first 指向当前头节点的下一个节点first = n;// 头节点的下一个节点为空if (n == null)// 置空尾结点last = null;else// 否则设置头节点的下一个节点的上一个节点为空n.prev = null;// 队列元素个数 - 1 --count;// 唤醒在 notFull 等待队列上的线程notFull.signal();return item;}private E unlinkLast() {Node<E> l = last;// 尾节点为空, 直接返回 nullif (l == null)return null;// 获取尾节点的上一个节点Node<E> p = l.prev;// 获取尾节点的数据E item = l.item;// 置空尾节点的数据l.item = null;// 设置尾结点的上一个节点为自身l.prev = l;// 设置尾节点的指针 last 指向当前尾结点的上一个节点last = p;// 尾结点的上一个节点为空if (p == null)// 置空头节点first = null;else// 设置尾结点的上一个节点的下一个节点为空p.next = null;// 队列元素个数 - 1 --count;// 唤醒在 notFull 等待队列上的线程notFull.signal();return item;}}
poll() 相关的方法的实现很简单, 最终就是调用到内部的 unlinkFirst() 和 unlinkLast() 方法, 而 2 个就是链表的节点删除操作。
而 poll() 相关的带超时时间的方法就不展开了, 原理和 LinkedBlockingQueue 一样, 利用了 Condition 的 awaitNanos 进行超时等待, 并在外面用 while 循环控制等待时的中断问题。
take() 相关的方法
public class LinkedBlockingDeque<E> {public E take() throws InterruptedException {// 调用到自身的 takeFirst 方法return takeFirst();}public E takeFirst() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lock();try {E x;// 通过 unlinkFirst 方法删除头节点, // 删除失败, 即返回结果为 null, 将当前线程阻塞在非空等待条件上// 等待唤醒后重新执行删除while ((x = unlinkFirst()) == null)notEmpty.await();return x;} finally {lock.unlock();}}public E takeLast() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lock();try {E x;// 通过 unlinkFirst 方法删除头节点, // 删除失败, 即返回结果为 null, 将当前线程阻塞在非空等待条件上// 等待唤醒后重新执行删除while ((x = unlinkLast()) == null)notEmpty.await();return x;} finally {lock.unlock();}}}
和 put() 方法一个套路, 先通过 lock 加锁, 然后 while 循环重试, 失败就 await 阻塞等待, 等待下次唤醒, 直至成功。
2.3.3 获取元素方法
LinkedBlockingDeque 提供了获取元素的方法主要有 2 个 element() 和 peek() 方法, 他们的区别在于, element() 方法在队列为空时会抛出异常, 而 peek() 方法则是返回 null。
public class LinkedBlockingDeque<E> {public E element() {// 调用 getFirst() 方法return getFirst();}public E peek() {// 调用 peekFirst() 方法return peekFirst();}public E getFirst() {// 调用 getFirst 方法结果还是调用到 peekFirst() 方法E x = peekFirst();if (x == null) throw new NoSuchElementException();return x;}public E getLast() {// 调用 getLast 方法结果还是调用到 peekLast() 方法E x = peekLast();if (x == null) throw new NoSuchElementException();return x;}public E peekFirst() {final ReentrantLock lock = this.lock;// 加锁lock.lock();try {// 头节点为空吗 ? 是的话返回 null, 否则返回头节点的数据return (first == null) ? null : first.item;} finally {lock.unlock();}}public E peekLast() {final ReentrantLock lock = this.lock;// 加锁lock.lock();try {// 尾节点为空吗? 是的话返回 null, 否则返回尾节点的数据return (last == null) ? null : last.item;} finally {lock.unlock();}}
}
获取元素前加锁, 防止并发问题导致数据不一致, 然后利用 first 和 last 2 个节点直接可以获得元素。
2.3.4 删除元素方法
public class LinkedBlockingDeque<E> {public boolean remove(Object o) {// 调用自身的 removeFirstOccurrence 方法return removeFirstOccurrence(o);}public boolean removeFirstOccurrence(Object o) {// 从头开始遍历// 需要移除的节点为 null, 直接抛异常if (o == null) return false;final ReentrantLock lock = this.lock;// 加锁lock.lock();try {// 从 first 向后开始遍历比较, 找到元素后调用 unlink 移除for (Node<E> p = first; p != null; p = p.next) {if (o.equals(p.item)) {unlink(p);return true;}}return false;} finally {lock.unlock();}}public boolean removeLastOccurrence(Object o) {// 从尾开始遍历if (o == null) return false;final ReentrantLock lock = this.lock;// 加锁lock.lock();try {// 从 last 向前开始遍历比较, 找到元素后调用 unlink 移除for (Node<E> p = last; p != null; p = p.prev) {if (o.equals(p.item)) {unlink(p);return true;}}return false;} finally {lock.unlock();}}void unlink(Node<E> x) {// 获取删除节点的上一个节点Node<E> p = x.prev;// 获取删除节点的下一个节点Node<E> n = x.next;// 需要删除的节点上一个节点为空, 需要删除的节点为头节点if (p == null) {unlinkFirst();} else if (n == null) {// 需要删除的节点的下一个节点为空, 需要删除对接的为尾节点unlinkLast();} else {// 把需要删除的节点的上一个节点的下一个节点的指针 指向 需要删除的节点的下一个节点p.next = n;// 把需要删除的节点的下一个节点的上一个节点的指针 指向 需要删除的节点的上一个节点n.prev = p;// 置空需要删除节点的数据x.item = null;// 队列中的元素个数 - 1--count;// 唤醒阻塞在 notFull 等待队列上的线程notFull.signal();}}
}
删除元素是从头 / 尾向两边进行遍历比较, 故时间复杂度为 O(n), 最后调用 unlink 把要移除元素的 prev 和 next 进行关联, 把要移除的元素从链中脱离, 等待下次 GC 回收。
3 总结
总体而言, LinkedBlockingDeque 的源码实现通过锁、条件等待和双向链表结构, 保证了在多线程环境中对队列的安全操作。
其本身可以作为一个双端队列使用, 也可以作为一个双端阻塞队列使用, 具有很好的灵活性。
4 转载
【细谈Java并发】谈谈LinkedBlockingDeque
相关文章:
【Java 集合】LinkedBlockingDeque
在开始介绍 LinkedBlockingDeque 之前, 我们先看一下 LinkedBlockingDeque 的类图: 从其中可以看出他直接实现了 BlockingDeque 接口, 而 BlockingDeque 又实现了 BlockingQueue 的接口, 所以它本身具备了队列的特性。 而实现 BlockingDeque 使其在 BlockingQueue 的基础上多了…...
【hacker送书第3期】OpenCV轻松入门:面向Python(第2版)
第3期图书推荐 内容简介作者简介图书目录专家推荐参与方式 内容简介 本书基于面向 Python 的 OpenCV(OpenCV for Python),介绍了图像处理的方方面面。本书以 OpenCV 官方文档的知识脉络为主线,并对细节进行补充和说明。书中不仅介绍了 OpenCV 函数的使用…...
手把手教你isPalindrome 方法在密码验证中的应用
在信息安全领域中,密码验证是一个极为重要的组成部分。一个强密码应具备足够的复杂性,以免遭到破解。而回文密码是一种具备特殊性质的密码,其正序和倒序相同,因此具有极高的安全性,并能发挥重要作用。在实际密码策略中…...
drf入门规范(二)
四 RESTful API规范 REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征性状态转移)。 它首次出现在2000年Roy Fielding的博士论文中。 RESTful是一种定义Web API接口的设计风格,尤其适用…...
使用Redis和Nginx分别实现限制接口请求频率
前言 为啥需要限制接口请求频率?这个是因为防止接口一直被刷,比如发送手机验证码的接口,一直被刷的话,费钱费资源的,至少做点基本的防护工作。以下分别使用Redis和Nginx实现限制接口请求频率方案。 一、基于Redis实现…...
ansible模块 (7-13)
模块 7、hostname模块: 远程主机名管理模块 ansible 192.168.10.202 -m hostname -a nameliu 8、copy模块: 用于复制指定的主机文件到远程主机的模块 常用参数: dest: 指出要复制的文件在哪,必须使用绝对路径。如果源目标是…...
MySQL概括与SQL分类
文章目录 一、计算机语言二、SQL语言三、数据库系统四、MySQL简介 一、计算机语言 二、SQL语言 三、数据库系统 四、MySQL简介...
微信小程序:wx:for 获取view点击的元素currentTarget.dataset为空
遍历数组渲染一组view通过bindtap事件获取点击的元素 解决办法: 在遍历时,设置data-item即可。 示例: <view wx:for"{{types}}" data-item"{{item}}"wx:key"key" bindtap"syntheActiveItem"c…...
Word的兼容性问题很常见,禁用兼容模式虽步不是最有效的,但可以解决兼容性问题
当你在较新版本的Word应用程序中打开用较旧版本的Word创建的文档时,会出现兼容性问题。错误通常发生在文件名附近(兼容模式)。兼容性模式问题(暂时)禁用Word功能,从而限制使用较新版本Word的用户编辑文档。…...
环境搭建及源码运行_java环境搭建_idea版本下载及安装
1、介绍 Idea是一款被广泛使用的Java集成开发环境,它提供了丰富的功能和工具来帮助开发人员更高效地编写和调试代码。作为一款开源软件,Idea不仅提供了基本的代码编辑、自动完成和调试功能,还支持大量的插件和扩展,可为开发人员提…...
jvm相关命令操作
查看jvm使用情况 jmap -heap PID 查看线程使用情况 jstack pid 查看当前线程数 jstack 21294 |grep -E (#[0-9]) -o -c 查看系统线程数 top -H top -Hp pid #查看具体的进程中的线程信息 使用 jps 命令查看配置了JVM的服务 查看某个进程JVM的GC使用情况 jstat -gc 进程…...
芋道前端框架上线之后发现element-ui的icon图标全部乱码
前言 最近发现线上有人反映图标全部是乱码,登录上去看确实乱码,刷新就好最后一顿搜,发现是sass版本不兼容导致的图标乱码问题 解决办法 1.先把sass升级到1.39.0 2.来到vue.config.js文件配置代码-如果是芋道前端框架不用配置自带 css: {lo…...
每个伦敦金投资者都应该练习的日线图交易
在伦敦金市场中,每个投资者都应该试着去做日线图的交易。有的人一听到日线图马上摇头,原因是日线图的价格跨度大,导致止损距离也变大,这样对投资者来说无疑是增加了风险。如果资金量大的投资者还好说,可以降低仓位&…...
高通平台开发系列讲解(USB篇)adb应用adbd分析
沉淀、分享、成长,让自己和他人都能有所收获!😄 在apps_proc/system/core/adb/adb_main.cpp文件中main()函数会调用adb_main()函数,然后调用uab_init函数 在uab_init()函数中,会创建一个线程,在线程中会调用init_functionfs()函数,利用ep0控制节点,创建ep1、ep2输…...
【上海大学数字逻辑实验报告】七、中规模元件及综合设计
一、实验目的 掌握中规模时序元件的测试。学会在Quartus II上设计序列发生器。 二、实验原理 74LS161是四位可预置数二进制加计数器,采用16引脚双列直插式封装的中规模集成电路,其外形如下图所示: 其各引脚功能为: 异步复位输…...
JVM内存结构Java内存模型Java对象模型
导图: https://naotu.baidu.com/file/60a0bdcaca7c6b92fcc5f796fe6f6bc9 1.JVM内存结构&&Java内存模型&&Java对象模型 1.1.JVM内存结构 1.2.Java对象模型 Java对象模型表示的是这个对象本身的存储模型,JVM会给这个类创建一个instanceKlass保存在方…...
Istio 社区周报(第一期):2023.12.11 - 12.17
欢迎来到 Istio 社区周报 Istio 社区朋友们,你们好! 我很高兴呈现第一期 Istio 社区周报。作为 Istio 社区的一员,每周我将为您带来 Istio 的最新发展、有见地的社区讨论、专业提示和重要安全新闻内容。 祝你阅读愉快,并在下一期中…...
质量图导向法解包裹之---计算边缘可靠性
在这之前需要我们知道像素点的可靠性 % 这反映了相位变化的平滑程度。以下是一个可能的实现,它使用了二阶差分来计算可靠性: function rel calculateReliability(wrappedPhase)% 应用高斯滤波减少噪声filteredImg imgaussfilt(wrappedPhase, 2); % 2 …...
C# WPF上位机开发(进度条操作)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 软件上面如果一个操作比较缓慢,或者说需要很长的时间,那么这个时候最好添加一个进度条,提示一下当前任务的进展…...
vulnhub-Tre(cms渗透)
靶机和kali都使用net网络,方便探测主机获取ip1.靶机探测 使用fping扫描net网段 靶机ip:192.168.66.130 2.端口扫描 扫描发现该靶机三个端口,ssh,还有两个web,使用的中间件也是不一样的,一个是apache&…...
Re解析(正则表达式解析)
正则表达式基础 元字符 B站教学视频: 正则表达式元字符基本使用 量词 贪婪匹配和惰性匹配 惰性匹配如下两张图,而 .* 就表示贪婪匹配,即尽可能多的匹配到符合的字符串,如果使用贪婪匹配,那么结果就是图中的情况三 p…...
HTML输出特殊字符详细方法
以下是部分特殊字符代码表,它们的完整应用代码格式为:&#;用下面的四位数字替换,将得到对应的符号。(注意:应用这些代码,编辑器应该切换到HTML模式) ☏260f ☎260e ☺263a ☻263b ☼263c ☽…...
《漫画算法》笔记——计算两个大数的和
例题: 输入:“123”,“234” 输出:“357” 思路: 使用数组,分别计算每一位上的加和,注意记录进位信息。 然后,将数组恢复成字符串,输出。 值得注意的是,加和…...
Python3.13版本改进规划
大家好,最近faster-cpython 项目的文档介绍了关于 Python 3.13 的规划,以及在 3.13 版本中将要实现的一些优化和改进。faster-python 是 Python 的创始人 Guido van Rossum 和他的团队提出的计划 ,目标是在四年内将 CPython 的性能提升五倍。…...
aws配置以及下载 spaceNet6 数据集
一:注册亚马逊账号 注册的时候,唯一需要注意的是信用卡绑定,这个可以去淘宝买,搜索aws匿名卡。 注册完记得点击登录,记录一下自己的账户ID哦! 二:登录自己的aws账号 2.1 首先创建一个用户 首…...
进阶之路:高级Spring整合技术解析
Spring整合 1.1 Spring整合Mybatis思路分析1.1.1 环境准备步骤1:准备数据库表步骤2:创建项目导入jar包步骤3:根据表创建模型类步骤4:创建Dao接口步骤5:创建Service接口和实现类步骤6:添加jdbc.properties文件步骤7:添加Mybatis核心配置文件步骤8:编写应用程序步骤9:运行程序 1.…...
【ArcGIS微课1000例】0081:ArcGIS指北针乱码解决方案
问题描述: ArcGIS软件在作图模式下插入指北针,出现指北针乱码,如下图所示: 问题解决 下载并安装字体(配套实验数据包0081.rar中获取)即可解决该问题。 正常的指北针选择器: 专栏介绍ÿ…...
uniapp运行到手机模拟器
第一步,下载MUMU模拟器 下载地址:MuMu模拟器官网_安卓12模拟器_网易手游模拟器 (163.com) 第二步,运行mumu模拟器 第三步,运行mumu多开器 第三步,查看abs 端口 第四步,打开HBuilder,如下图,将…...
基于PHP的蛋糕购物商城系统
有需要请加文章底部Q哦 可远程调试 基于PHP的蛋糕购物商城系统 一 介绍 此蛋糕购物商城基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈:phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销…...
嵌入式中的定时器概念
定时器概述 定时器是嵌入式系统中常用的一种外设,它可以产生一定的时间间隔、延时、定时等功能,广泛应用于定时、计数、脉冲宽度调制(PWM)等领域。 具体而言,定时器可以实现以下功能: 计时:定时器可以用来实现延时操作,例如等待外部设备的稳定、等待数据的接收等,也可以…...
软件上传网站/东莞seo代理
最近数码圈新机发布的少,但操作系统却打的火热。这边鸿蒙OS2.0刚刚公测,那边Android 12系统就正式登场了。谷歌正式发布Android 12(1)开放的系统风格与操作界面这么多年以来,国产手机用的基本上都是安卓系统,虽然它们在此基础上设…...
手机微信可以做网站吗/网络营销策划方案ppt模板
django-check-seo为 Django CMS 用户替换 Yoast 或 SEMrush 的部分功能。换句话说,django-check-seo 会告诉你你的网站 SEO 是否存在问题。同时,这些建议对很多搜索引擎都有效。安装如果你的网站是基于 django-cms 的,并且 Python 和 Django …...
网站上面的彩票快3怎么做/在线外链发布工具
工作步骤:下载内核源码和相关的软件包;把ipvs补丁Patch到内核源码中;重新编译支持ipvs的内核;启用新内核;安装ipvs管理工具ipvsadm;配置LVS,构建负载均衡集群。 (1) 下载安装所需组件: 标准内…...
一级a做爰视频安全网站/网络推广外包要多少钱
ALLEXCEPT函数ALLEXCEPT函数属于“筛选”类函数,隶属于“表函数”,在ALL函数系列家族中,其地位是不可或缺的。EXCEPT翻译成中文是什么意思?表示:除了的意思。因此,这个函数所表达的意思顾名思义,…...
商贸公司网站建设/成人短期电脑培训班学费
本资源整理了一些在学习过程中阅读过的且感觉不错的论文,涉及聚类,向量召回,对话系统,对话状态管理,机器学习,语言模型数据集,文本相似度/匹配/分类,深度学习,语音系统&a…...
南京高端网站建设工作室/百度搜索引擎推广步骤
web应用程序 本质 socket服务端 浏览器本质是一个socket客户端1. 服务器程序 socket请求 接受HTTP请求,发送HTTP响应。比较底层,繁琐,有专用的服务器软件,如:Apache Nginx2. 应用程序,实现具体逻辑WSGI&…...