高效并发编程:Java阻塞队列深度解析与最佳实践

1.阻塞队列的基本概念与应用场景

1.1 阻塞队列的定义

阻塞队列(BlockingQueue)是Java并发包中的一个接口,它支持两个附加操作:当队列为空时,获取元素的线程会等待队列变为非空;当队列满时,存储元素的线程会等待队列可用。这种队列通常用于生产者和消费者的场景,其中生产者不能在意想不到的速度填充队列,以至于消耗所有可用的内存资源。

public interface BlockingQueue<E> extends Queue<E> {void put(E e) throws InterruptedException;E take() throws InterruptedException;// ... 其他方法省略
}

1.2 阻塞队列的主要用途

阻塞队列最典型的案例就是生产者-消费者模型,在这种模型中,生产者将对象放入队列,消费者则从队列中取出这些对象。使用阻塞队列可以有效地协调生产者和消费者之间的速度,如果生产者比消费者快,队列会满,这会让生产者在尝试放入元素时阻塞。如果消费者比生产者快,队列会空,这会让消费者在尝试取出元素时阻塞。
除了平衡生产与消费的节奏外,阻塞队列还在很多异步处理的场景中发挥作用,如并行计算、消息处理系统等。

2.阻塞队列的核心方法解析

2.1 插入方法

put(E e): 一个用于插入元素的方法,如果队列满了,它将等待空间变得可用。

blockingQueue.put("Value");

2.2 移除方法

take(): 用于移除并返回队列头部的元素,如果队列为空,它将等待直至元素变得可用。

String value = blockingQueue.take();

2.3 检查方法

peek(): 用于检查队列头部的元素而不移除,此方法不会阻塞。

String nextValue = blockingQueue.peek();

2.4 阻塞与超时处理机制

阻塞队列提供了超时机制的方法如 offer() 和 poll(),允许定义一个等待的时间,这为防止因无休止的等待而使程序无法继续执行提供了解决方案。

boolean success = blockingQueue.offer("Value", 500, TimeUnit.MILLISECONDS);
String value = blockingQueue.poll(500, TimeUnit.MILLISECONDS);

3.阻塞队列的实现原理

3.1 同步器(Synchronizer)的角色

在所有阻塞队列的背后,都运用了同步器的概念,以确保线程安全。同步器是实现锁和其他同步类的有用基础设施。以ReentrantLock为例,让我们概述它如何在队列操作中被利用:

public class MyBlockingQueue<E> {private final ReentrantLock lock = new ReentrantLock();private final LinkedList<E> list = new LinkedList<>();private final Condition notEmpty = lock.newCondition();private final Condition notFull = lock.newCondition();public void put(E e) throws InterruptedException {lock.lock();try {while (list.size() == MAX_CAPACITY) {notFull.await();}list.add(e);notEmpty.signal();} finally {lock.unlock();}}public E take() throws InterruptedException {lock.lock();try {while (list.size() == 0) {notEmpty.await();}E e = list.remove();notFull.signal();return e;} finally {lock.unlock();}}
}

在这个例子中的put和take方法都使用了锁来保证在同一时间只有一个线程可以执行特定代码区域。同时也用到了条件(Conditions)提供了一种能力,让线程声明它在继续前,需要的某个条件为真(例如,“not full"或"not empty”)。

3.2 队列内部结构与数据流转

阻塞队列内部通常使用链表或数组来储存数据。例如,ArrayBlockingQueue 使用一个数组,而LinkedBlockingQueue 用一个链表节点。当一个元素被插入或移除时,队列使用锁防止多个线程的干扰,并利用条件来处理是否需要阻塞线程或唤醒等待的线程。下面是一个基于数组的阻塞队列的简化示例,展示数据结构和同步机制:

public class ArrayBlockingQueue<E> {private final E[] array;private int takeIndex;private int putIndex;private int count;// ... 省略锁和其他成员变量public void put(E e) throws InterruptedException {// ... 锁和条件的使用while (count == array.length) {// 阻塞条件}enqueue(e); // 实际的入队操作// ... 状态修改与线程唤醒}public E take() throws InterruptedException {// ... 锁和条件的使用while (count == 0) {// 阻塞条件// 阻塞条件}E x = dequeue(); // 实际的出队操作// ... 状态修改与线程唤醒return x;}private void enqueue(E e) {array[putIndex] = e;if (++putIndex == array.length) {putIndex = 0;}count++;notEmpty.signal();}private E dequeue() {E x = array[takeIndex];array[takeIndex] = null;if (++takeIndex == array.length) {takeIndex = 0;}count--;notFull.signal();return x;}// ... 其他方法和内部类省略
}

在enqueue方法中,如果putIndex达到数组的末端,则会循环回到数组的起始,形成一个环形结构,以最大化数组的使用。同样地,在dequeue中,索引以同样的方式操作。这种方式优化了队列的储存能力,使其对数组的使用变得连续和高效。
为了防止数组在入队和出队时超出界限,我们使用count来记录队列中元素的数量。当队列为空(count == 0)或满(count == array.length)时,相应的线程将会阻塞等待。
现在,这个简化的模型展示了阻塞队列如何依靠锁来保证并发控制,并使用条件(conditional variables)来同步线程间的协作。

4.Java并发包中的阻塞队列类详解

4.1 ArrayBlockingQueue的结构与特征

ArrayBlockingQueue是一个由数组支撑的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。可选地,可以在构造函数内部定义队列的公平性,如果公平性设定为true,那么等待时间最长的线程会优先得到处理。

ArrayBlockingQueue<Integer> abq = new ArrayBlockingQueue<>(10, true);

4.1.1 公平性对性能的影响

尽管公平锁能够防止线程饥饿,但是它们比非公平锁对性能的影响更大,因为公平锁会降低吞吐量。每次插入或删除操作时,它都需要确保等待队列中的线程按照它们请求访问的顺序获得锁。

4.1.2 ArrayBlockingQueue的适用场景

由于ArrayBlockingQueue的内部是一个固定长度的数组,因此它适用于有明确大小限制的场景,且当您需要平衡生产者和消费者的工作速度时,此阻塞队列非常适合。

4.2 LinkedBlockingQueue的并发优化

LinkedBlockingQueue是一个基于已链接节点的,可选择有界或无界的阻塞队列。在性能调优和并发水平方面,它通常比ArrayBlockingQueue更灵活。

4.2.1 分离锁技术分析

LinkedBlockingQueue内部使用了两个锁——一个用于入队,一个用于出队。这意味着入队和出队操作可以并发进行,大大提升了队列的吞吐量。

LinkedBlockingQueue<Integer> lbq = new LinkedBlockingQueue<>();

4.2.2 LinkedBlockingQueue的实践案例

在并发程序设计中,LinkedBlockingQueue通常用来实现生产者-消费者模式,其中多个线程产生任务,另外多个线程消费这些任务。分离锁增加了并发度,使得在高负载时也能保持高性能。

4.3 PriorityBlockingQueue的优先级排序机制

该队列是一个无界的并发队列,它使用优先级堆来对元素进行排序。元素需要实现Comparable接口,队列利用元素的自然顺序或者根据构造器提供的Comparator确定出队的顺序。

PriorityBlockingQueue<Task> pbq = new PriorityBlockingQueue<>(initialCapacity, comparator);

4.3.1 compareTo方法的实现与应用

为了在PriorityBlockingQueue中使用自定义的排序,元素类需要实现Comparable接口,并重写compareTo方法来定义排序逻辑。

public class Task implements Comparable<Task> {private int priority;// ...public int compareTo(Task o) {return Integer.compare(this.priority, o.getPriority());}
}

4.3.2 应用PriorityBlockingQueue的示例

一个常见的使用场景是任务调度,其中优先级更高的任务应该先被执行。PriorityBlockingQueue确保了最紧急的任务总是先被处理。

4.4 DelayQueue的延迟策略

DelayQueue是一个无界阻塞队列,只有在元素的延迟到期时才能从队列中取出。这一特性使它非常适合于实现缓存失效特性和定时任务调度。

4.4.1 DelayQueue的工作原理

队列中的元素必须实现Delayed接口,并需要定义一个getDelay方法来指定元素到期的时间。

public class DelayedElement implements Delayed {private final long delayTime; // 延迟时间private final long expire; // 到期时间// ... 构造器和其他方法@Overridepublic long getDelay(TimeUnit unit) {long diff = expire - System.currentTimeMillis();return unit.convert(diff, TimeUnit.MILLISECONDS);}@Overridepublic int compareTo(Delayed o) {if (this.expire < ((DelayedElement) o).expire) {return -1;}if (this.expire > ((DelayedElement) o).expire) {return 1;}return 0;}
}

DelayQueue使用这些信息来确保只有过期元素才会出队。例如,定时任务的执行,或者使用在缓存中,确保对象只在它们有效的时候才存在于缓存中。

4.4.2 延迟队列的典型应用

在实际应用中,像是缓存系统中自动删除过期条目、任务调度中延迟执行任务等场景都可以使用DelayQueue。

4.5 SynchronousQueue的即时交互特性

SynchronousQueue是一种没有内部容量的队列,每个插入操作都必须等待一个相应的删除操作,反之亦然。因此,SynchronousQueue并不真正存储元素,更多的像是一种线程间交传的机制。

SynchronousQueue<Integer> synQueue = new SynchronousQueue<>();

4.5.1 SynchronousQueue的特别之处

SynchronousQueue的一个关键特性是它不存储元素。如果没有任何线程等待获取元素,那么试图放入元素会阻塞,直至有另一个线程来取走它。

4.5.2 使用SynchronousQueue进行线程间通信

这种队列通常用于直接的线程间通信。比如分布式系统中,你可能使用SynchronousQueue在工作者线程间直接交换任务。

4.6 LinkedTransferQueue的特性与应用

LinkedTransferQueue是一个由链表结构组成的无界阻塞队列。除了常见的阻塞队列操作外,它还提供了transfer和tryTransfer方法,用于即时的元素传递。

LinkedTransferQueue<Integer> ltq = new LinkedTransferQueue<>();

它就像一个LinkedBlockingQueue和SynchronousQueue的混合体,既能存储元素,也能直接交换元素。

4.7 LinkedBlockingDeque双端队列的灵活性

最后,LinkedBlockingDeque是一个可选有界的阻塞双端队列,允许线程从队列的两端插入和移除元素,这为某些特定的使用场景提供了便利。

LinkedBlockingDeque<Task> deque = new LinkedBlockingDeque<>();

它能够从两端进行插入和移除操作,使得它成为一种扩展了功能的队列,可以灵活应对需求的变化。

5.阻塞队列的使用技巧与最佳实践

5.1 阻塞队列在生产者-消费者模型中的应用

一个常见的应用场景是生产者-消费者模型,在这种场景中,生产者创建数据放入队列,消费者从队列中取出数据进行处理。阻塞队列自然地协调了生产者和消费者之间的速度,它确保当队列满时生产者会等待,而队列空时消费者会等待。
常见的做法是生产者和消费者分别在不同的线程或者线程池中执行,以此来提高整个系统的并行度:

ExecutorService producerPool = Executors.newFixedThreadPool(N_PRODUCERS);
ExecutorService consumerPool = Executors.newFixedThreadPool(N_CONSUMERS);
for (int i = 0; i < N_PRODUCERS; i++) {producerPool.submit(() -> {while (true) {// 生产数据queue.put(produceData());}});
}
for (int i = 0; i < N_CONSUMERS; i++) {consumerPool.submit(() -> {while (true) {// 消费数据consumeData(queue.take());}});
}

5.2 多线程环境下阻塞队列的使用注意事项

在使用阻塞队列时,要注意线程的中断策略。在等待插入或移除操作的阻塞过程中,线程可能会被中断,正确的中断处理策略可以避免资源泄露或者不一致的状态。以下是处理中断的一种推荐方法:

try {queue.put(data);
} catch (InterruptedException e) {// 线程被中断的处理Thread.currentThread().interrupt(); // 重新设置中断状态return; // 或根据情况进行其他处理,例如重试或者退出
}

5.3性能调优与监控

性能优化是使用阻塞队列时的关键考虑点。你可以通过调整线程池大小、队列容量和使用正确类型的阻塞队列来优化性能。例如,如果你的应用场景涉及多生产者和多消费者,你可能会考虑使用LinkedBlockingQueue而不是ArrayBlockingQueue,因为前者在多线程环境下具有更好的吞吐量。
同时,监控队列的状态也非常重要,它可以帮助你理解系统性能及时发现潜在的问题,例如,队列的大小、增长趋势、丢弃的任务数等。

6.码分析阻塞队列的具体实现

结合源码来分析是理解Java阻塞队列内在机制的绝佳方式。通过具体的代码示例,我们可以更深入地理解前面提到的概念和细节。

6.1 ArrayBlockingQueue源码剖析

ArrayBlockingQueue在Java的并发包中是一种经典的有界队列实现。以下是它源码的简化版本,侧重于其核心功能:

public class ArrayBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {/ The queued items */private final E[] items;/ items index for next take, poll, peek or remove /private int takeIndex;/** items index for next put, offer, or add/private int putIndex;/ Number of elements in the queue */private int count;/ Main lock guarding all access /final ReentrantLock lock;/** Condition for waiting takes/private final Condition notEmpty;/** Condition for waiting puts */private final Condition notFull;// ... 构造函数和其他方法省略public void put(E e) throws InterruptedException {checkNotNull(e);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == items.length)notFull.await();enqueue(e);} finally {lock.unlock();}}public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == 0)notEmpty.await();return dequeue();} finally {lock.unlock();}}private void enqueue(E e) {// circularly increment indexitems[putIndex] = e;if (++putIndex == items.length)putIndex = 0;count++;notEmpty.signal();}private E dequeue() {// similar circularly decrement on takeIndexE x = items[takeIndex];items[takeIndex] = null; // for GCif (++takeIndex == items.length)takeIndex = 0;count--;notFull.signal();return x;}// ...其他方法和内部类
}

在ArrayBlockingQueue中,enqueue方法在队列尾部添加元素,dequeue方法从头部移除元素。通过循环索引的方式优化了数组的使用,使得队列的前端和后端可以在数组的任意位置。当putIndex和takeIndex相遇时,这种设计允许队列无缝地从数组末尾回绕到开始位置。
使用两个条件变量notEmpty和notFull分别对空和满的情况进行线程阻塞和唤醒,这允许线程在条件不满足时等待(比如空队列或满队列),并且在条件改变时得到通知,从而恢复执行。

6.2 LinkedBlockingQueue源码解读

LinkedBlockingQueue则使用链表节点结构存储元素,初始容量几乎无限制,但可选择定义其界限。它的实现利用了两把锁——一把用于控制入队操作,一把用于出队操作,从而实现了更好的并发性。

public class LinkedBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {// ...节点定义和其他成员变量private final int capacity;private final AtomicInteger count = new AtomicInteger();transient Node<E> head;private transient Node<E> last;private final ReentrantLock takeLock = new ReentrantLock();private final Condition notEmpty = takeLock.newCondition();private final ReentrantLock putLock = new ReentrantLock();private final Condition notFull = putLock.newCondition();// ...构造函数和其他方法public void put(E e) throws InterruptedException {if (e == null) throw new NullPointerException();// Note: Convention in all put/take/etc is to preset local var// holding count negative to indicate failure unless set.int c = -1;Node<E> node = new Node<E>(e);final ReentrantLock putLock = this.putLock;putLock.lockInterruptibly();try {while (count.get() == capacity) {notFull.await();}enqueue(node);c = count.getAndIncrement();if (c + 1 < capacity)notFull.signal();} finally {putLock.unlock();}if (c == 0)signalNotEmpty();}private void signalNotEmpty() {final ReentrantLock takeLock = this.takeLock;takeLock.lock();try {notEmpty.signal();} finally {takeLock.unlock();}}private void enqueue(Node<E> node) {// Always put at lastlast = last.next = node;}public E take() throws InterruptedException {E x;int c = -1;final AtomicInteger count = this.count;final ReentrantLock takeLock = this.takeLock;takeLock.lockInterruptibly();try {while (count.get() == 0) {notEmpty.await();}x = dequeue();c = count.getAndDecrement();if (c > 1)notEmpty.signal();} finally {takeLock.unlock();}if (c == capacity)signalNotFull();return x;}private E dequeue() {// Always take from headNode<E> h = head;Node<E> first = h.next;h.next = h; // Help GChead = first;E x = first.item;first.item = null;return x;}// ...其他方法和内部类
}

在LinkedBlockingQueue中,enqueue方法将新节点添加到尾节点的下一个位置,并更新last指针。dequeue方法则从头结点的下一个节点取出元素,因为头节点是一个空的哑元节点,用于简化边界检查和获取锁的过程。
两个锁putLock和takeLock保证了入队和出队操作的线程安全性,且延续了先前在ArrayBlockingQueue讨论中的条件变量模型,使用它们分别处理非满和非空的阻塞情况。

6.3 基于源码深度优化阻塞队列性能

了解了阻塞队列如ArrayBlockingQueue和LinkedBlockingQueue的源码实现后,我们可以考虑如何根据应用场景对它们进行性能优化。优化可以涉及多个方面:

  • 在有明确容量限制的环境中更青睐ArrayBlockingQueue。
  • 在需要高吞吐量的环境中使用LinkedBlockingQueue,特别是双向锁带来的并发优势。
  • 调整条件变量的使用,或者完全替换同步机制,比如使用java.util.concurrent.locks包中的其他锁实现。
  • 监测和分析锁竞争情况,以及等待时间,为调优提供数据基础。

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

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

相关文章

RAG概述(二):Advanced RAG 高级RAG

目录 概述 Advanced RAG Pre-Retrieval预检索 优化索引 增强数据粒度 粗粒度 细粒度 展开说说 优化索引 Chunk策略 Small2Big方法 元数据 引入假设性问题 对齐优化 混合检索 查询优化 查询扩展 查询转换 Post-Retrieval后检索 参考 概述 Native RAG&#…

转义字符知识点

转义字符的使用 什么是转义字符&#xff1f; 它是字符串的一部分&#xff0c;用来表示一些特殊含义的字符 比如&#xff1a;在字符串中表现&#xff0c;单引号&#xff0c;引号&#xff0c;空行等等 固定写法 \字符 不同的\和字符的组合表示不同的含义. 常用的转义字符 附…

关于Springboot同时上传文件与其他参数

Springboot同时上传文件与其他参数 http请求数据传递分为请求体和请求参数。跟在url后面的为请求参数&#xff0c;文件上传和json数据放在请求体中。请求参数格式固定&#xff0c;后台可直接解析&#xff1b;请求体中的数据后台需要根据content-type字段按照特定格式解析。con…

shell从入门到精通(23)贪婪匹配、非贪婪以及独占模式

文章目录 贪婪与非贪婪的区别示例贪婪匹配的特点--自动回溯以满足匹配独占模式总结贪婪与非贪婪的区别 在正则表达式中,贪婪匹配和非贪婪匹配是指匹配模式下的不同行为。 贪婪匹配: 贪婪匹配会尽可能多地匹配符合模式的字符。换句话说,它会一直匹配直到无法再匹配为止。例…

springboot vue 开源 会员收银系统 (4) 门店模块开发

前言 完整版演示 前面我们对会员系统 springboot vue 开源 会员收银系统 (3) 会员管理的开发 实现了简单的会员添加 下面我们将从会员模块进行延伸 门店模块的开发 首先我们先分析一下常见门店的管理模式 常见的管理形式为总公司 - 区域管理&#xff08;若干个门店&#xff…

Java实现插入排序、冒泡排序、堆排序、希尔排序、选择排序、优先队列排序、快速排序、归并排序(详细注释,原理解析)

插入排序 package learn;import java.util.Arrays;/** 每次都将当前元素插入到左侧已经排序的数组中&#xff0c;使得插入之后左侧数组依然有序。* 速度优于选择排序*/ public class InsertSort {public static void insertSort(int[] a) {int n a.length;for (int i 1; i &…

Elasticsearch优点和缺点以及要点和难点具体应用

Elasticsearch是一个开源、分布式、实时的搜索和分析引擎,它位于Elastic Stack(以前称为ELK Stack)的核心。以下是关于Elasticsearch的一些主要特点和功能: 1.分布式和可扩展性:Elasticsearch是分布式的,可以轻松扩展到多个节点以处理大规模数据集和高并发请求。通过将数…

C语言 | Leetcode C语言题解之第113题路径总和II

题目&#xff1a; 题解&#xff1a; int** ret; int retSize; int* retColSize;int* path; int pathSize;typedef struct {struct TreeNode* key;struct TreeNode* val;UT_hash_handle hh; } hashTable;hashTable* parent;void insertHashTable(struct TreeNode* x, struct Tr…

C++干货 --类和对象(二)

前言&#xff1a; 上文中&#xff0c;我们介绍了类这一重要知识点&#xff0c;包括为什么要有类、类的使用方法、封装、以及对象实例化。详情可以去看我的文章&#xff1a;写文章-CSDN创作中心C干货 --类和对象(一)-CSDN博客写文章-CSDN创作中心 这篇文章&#xff0c;我们简单…

我学会了用插件来辅助PostgreSQL,可拷,很刑!

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯及Greenplum备份恢复&#xff0c; 安装迁移&#xff0c;性能优化、故障…

【蓝桥杯省赛真题44】python计算N+N的值 中小学青少年组蓝桥杯比赛 算法思维python编程省赛真题解析

目录 python计算NN的值 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python计算NN的值 第十四届蓝桥杯青少年组python省赛真题 一、题目要求…

Makefile学习笔记18|u-boot顶层Makefile04

Makefile学习笔记18|u-boot顶层Makefile04 希望看到这篇文章的朋友能在评论区留下宝贵的建议来让我们共同成长&#xff0c;谢谢。 这里是目录 C语言标准 # With the move to GCC 6, we have implicitly upgraded our language # standard to GNU11 (see https://gcc.gnu.org/…

WordPress--解决迁移后页面404(打不开)的问题

原文网址&#xff1a;WordPress--解决迁移后页面404的问题_IT利刃出鞘的博客-CSDN博客 简介 本文介绍WordPress迁移后部分页面404(打不开)的解决方案。 方案&#xff1a;修改固定链接 简介 要在刚建好网站&#xff0c;还没导入数据之前修改固定链接&#xff0c;否则可能导…

【教学类-58-05】黑白三角拼图05(2-10宫格,每个宫格随机1张-6张,带空格纸,1页3张黑白3张白卡)

背景需求&#xff1a; 【教学类-58-04】黑白三角拼图04&#xff08;2-10宫格&#xff0c;每个宫格随机1张-6张&#xff0c;带空格纸&#xff0c;1页6张黑白&#xff0c;1张6张白卡&#xff09;-CSDN博客文章浏览阅读582次&#xff0c;点赞16次&#xff0c;收藏3次。【教学类-58…

Mac安装pytorch

先下载 Anaconda | The Operating System for AI 网速慢&#xff0c;用中国大陆镜像&#xff1a;NJU Mirror 之前装python3时用的是pip3&#xff0c;这里说一下这pip与conda的区别 Conda和pip都是Python包管理工具&#xff0c;用于安装和管理Python包 包管理范围&#xff1a…

汽车数据应用构想(一)

自从电动汽车GB/T32960标准颁布&#xff0c;要求所有电动汽车必须上传数据开始&#xff0c;各车厂就开始花费大量的人力物力&#xff0c;用于数据的上传与存储。同时随着智能化、网联化的趋势&#xff0c;不断丰富上传数据的内容与数量。数据已成为车厂的重要资产&#xff0c;但…

Day16

Day16 一、迭代器 深入迭代器-foreach的底层 for (String element : list) {System.out.println(element);}底层&#xff1a; //使用foreach循环遍历集合的底层实现&#xff1a;String element;for(Iterator it list.iterator();it.hasNext();System.out.println(element))e…

微服务:eureka的搭建,以及服务注册、服务发现、负载均衡

eureka 搭建 新建一个Module,maven项目&#xff0c;导入依赖。 <dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency><…

月入10万+管道收益,揭秘旅游卡运营的5个阶段!

网上的项目确实繁多&#xff0c;只要深入研究&#xff0c;总能找到适合自己的赚钱方式。在互联网上运营&#xff0c;关键在于理解其底层逻辑。旅游卡项目&#xff0c;就是一个能实现月入10万的实例。接下来&#xff0c;我将为你揭密旅游卡运营的5个阶段&#xff1a; 1、先赚成…

24年湖南教资认定即将开始,别被照片卡审!

24年湖南教资认定即将开始&#xff0c;别被照片卡审&#xff01;