深入浅出Java多线程(十三):阻塞队列

这篇具有很好参考价值的文章主要介绍了深入浅出Java多线程(十三):阻塞队列。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

引言


大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第十三篇内容:阻塞队列。大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!!

在多线程编程的世界里,生产者-消费者问题是一个经典且频繁出现的场景。设想这样一个情况:有一群持续不断地生产资源的线程(我们称之为“生产者”),以及另一群持续消耗这些资源的线程(称为“消费者”)。他们共享一个缓冲池,生产者将新生成的资源存入其中,而消费者则从缓冲池中取出并处理这些资源。这种设计模式有效地简化了并发编程的复杂性,一方面消除了生产者与消费者类之间的代码耦合,另一方面通过解耦生产和消费过程,使得系统可以更灵活地分配和调整负载。

然而,在实际实现过程中,尤其是在Java等支持多线程的语言中,直接操作共享变量来同步生产和消费行为会带来诸多挑战。如果没有采取适当的同步机制,当多个生产者或消费者同时访问缓冲池时,很容易造成数据竞争、重复消费甚至是死锁等问题。例如,当缓冲池为空时,消费者应被阻塞以免无谓地消耗CPU资源;而当缓冲池已满时,则需要阻止生产者继续添加元素,转而唤醒等待中的消费者去消耗资源。

为了解决上述难题,Java标准库提供了强大的工具——java.util.concurrent.BlockingQueue接口及其实现类。阻塞队列作为Java并发编程的重要组成部分,允许开发者无需手动处理复杂的线程同步逻辑,只需简单地向队列中添加或移除元素,即可确保线程安全的操作。无论是插入还是获取元素的操作,若队列当前状态不允许该操作执行,相应的线程会被自动阻塞,直至条件满足时再被唤醒。

举例来说,我们可以创建一个ArrayBlockingQueue实例,设置其容量大小,并让生产者线程通过调用put()方法将新生产的对象放入队列,如果队列已满,put()方法会阻塞生产者线程直到有消费者线程从队列中移除了某个元素腾出空间为止:

ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10); // 创建一个容量为10的阻塞队列

// 生产者线程
new Thread(() -> {
    for (int i = 0; ; i++) { // 不断生产资源
        try {
            queue.put(i); // 尝试将资源放入队列,若队列满则阻塞
            System.out.println("生产者放入了一个资源:" + i);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            break;
        }
    }
}).start();

// 消费者线程
new Thread(() -> {
    while (true) { // 不断消费资源
        try {
            Integer resource = queue.take(); // 尝试从队列中取出资源,若队列空则阻塞
            System.out.println("消费者消费了一个资源:" + resource);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            break;
        }
    }
}).start();

总之,借助阻塞队列这一特性,程序员能更专注于业务逻辑,而不必过分担忧底层的线程同步问题,从而极大地提升了并发程序的设计效率和可靠性。在接下来的内容中,我们将深入探讨阻塞队列的具体操作方法、多种实现类及其内部工作原理,并结合实际案例来进一步理解它在Java多线程编程中的核心价值。

阻塞队列作用


阻塞队列的由来与作用在多线程编程中扮演着至关重要的角色。其诞生源于解决生产者-消费者问题这一经典的并发场景,它有效地降低了开发复杂度,并确保了数据交换的安全性。

在传统的生产者-消费者模式下,假设存在多个生产者线程和消费者线程,它们共享一个有限容量的缓冲池(或称为队列)。生产者线程负责生成资源并将其存入缓冲池,而消费者线程则从缓冲池取出资源进行消费。如果直接使用普通的非同步队列,在多线程环境下进行资源的存取操作时,可能会出现以下问题:

  1. 线程安全问题:当多个线程同时访问同一个队列时,可能出现竞态条件导致的数据不一致,例如重复消费、丢失数据或者数据状态错乱。
  2. 死锁与活跃性问题:在没有正确同步机制的情况下,生产者和消费者线程可能陷入互相等待对方释放资源的状态,从而导致死锁;或者当缓冲区已满/空时,线程因无法继续执行而进入无限期等待状态,影响系统整体的效率和响应性。
  3. 自定义同步逻辑复杂:为了解决上述问题,开发者需要自行编写复杂的等待-通知逻辑,即当队列满时阻止生产者添加元素,唤醒消费者消费;反之,当队列空时阻止消费者获取元素,唤醒生产者填充资源。这些逻辑容易出错且不易维护。

Java平台通过引入java.util.concurrent.BlockingQueue接口及其一系列实现类,大大简化了生产者-消费者问题的解决方案。BlockingQueue不仅提供了线程安全的队列访问方式,而且自动处理了上述的各种同步问题,使得生产者和消费者能够自然地协作,无需关注底层的线程同步细节。

举例来说,下面是一个使用ArrayBlockingQueue作为共享资源容器的简单生产者-消费者示例:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class BlockingQueueExample {
    static final int QUEUE_CAPACITY = 10;
    static ArrayBlockingQueue<Integer> sharedQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);

    public static void main(String[] args) {
        Thread producerThread = new Thread(() -> produce());
        Thread consumerThread = new Thread(() -> consume());

        producerThread.start();
        consumerThread.start();

        try {
            producerThread.join();
            consumerThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    static void produce() {
        for (int i = 0; ; i++) {
            try {
                sharedQueue.put(i);
                System.out.println("生产者放入了一个元素:" + i);
                TimeUnit.MILLISECONDS.sleep(100); // 模拟生产间隔
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }

    static void consume() {
        while (true) {
            try {
                Integer item = sharedQueue.take();
                System.out.println("消费者消费了一个元素:" + item);
                TimeUnit.MILLISECONDS.sleep(150); // 模拟消费间隔
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

在这个例子中,生产者线程调用put()方法将整数元素添加到ArrayBlockingQueue中,当队列满时,该方法会阻塞生产者直到有空间可用。消费者线程则通过调用take()方法从队列中移除并消费元素,当队列为空时,消费者会被阻塞直至有新的元素被加入。这样,阻塞队列充当了协调生产者和消费者工作节奏的核心组件,保证了整个系统的稳定性和高效运行。

阻塞队列的操作方法详解


阻塞队列的操作方法详解是理解和使用Java并发包中java.util.concurrent.BlockingQueue的关键部分。它提供了一系列丰富的方法来插入、移除和检查元素,这些方法在处理多线程环境下共享数据时确保了线程安全,并能够根据不同的需求采取不同的策略。

抛出异常操作:

  • add(E e):如果尝试向满的队列添加元素,则抛出IllegalStateException("Queue full")异常。
  • remove():若队列为空则抛出NoSuchElementException异常,用于移除并返回队列头部的元素。
  • element():返回但不移除队列头部的元素,同样在队列为空时抛出NoSuchElementException异常。

返回特殊值操作:

  • offer(E e):尝试将元素放入队列,如果队列已满则返回false,否则返回true表示成功加入。
  • poll():尝试从队列中移除并返回头部元素,若队列为空则返回null
  • peek():查看队列头部元素而不移除,队列为空时也返回null

一直阻塞操作:

  • put(E e):将指定元素添加到队列中,如果队列已满,则当前线程会被阻塞直到有空间可用。
  • take():从队列中移除并返回头部元素,如果队列为空,调用此方法的线程会阻塞等待其他线程存入元素。

超时退出操作:

  • offer(E e, long timeout, TimeUnit unit):试图将元素添加到队列,若在给定超时时间内仍无法加入,则返回false,否则返回true
  • poll(long timeout, TimeUnit unit):试图从队列中移除并返回一个元素,若在给定超时时间内队列依然为空,则返回null

举例说明,以下代码展示了如何使用BlockingQueue的一些基本操作:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class BlockingQueueDemo {
    static final int QUEUE_CAPACITY = 5;
    static ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);

    public static void main(String[] args) throws InterruptedException {
        // 使用put()方法添加元素,当队列满时阻塞生产者
        for (int i = 0; i < 7; i++) {
            queue.put("Item " + i);
            System.out.println("已放入: " + "Item " + i);
        }

        // 使用take()方法消费元素,当队列空时阻塞消费者
        while (!queue.isEmpty()) {
            String item = queue.take();
            System.out.println("已取出: " + item);
        }

        // 使用offer()方法尝试添加,不会阻塞生产者
        if (!queue.offer("额外项"1, TimeUnit.SECONDS)) {
            System.out.println("添加失败,队列已满或超时");
        }
    }
}

在上述示例中,ArrayBlockingQueue的容量为5,当尝试通过put()方法添加第6个元素时,生产者线程将会被阻塞;而消费者线程通过take()方法逐个取出元素时,如果遇到队列为空的情况,也会被阻塞直至新的元素加入。此外,我们还演示了offer()方法配合超时参数,在指定的时间内尝试添加元素,超过这个时间限制仍未成功添加时,方法会立即返回结果而不是继续阻塞。

阻塞队列的实现类


阻塞队列的实现类解析是深入理解Java并发编程中BlockingQueue接口的关键环节。Java标准库提供了多种阻塞队列的实现,每种都有其特定的设计和适用场景。

ArrayBlockingQueue: ArrayBlockingQueue基于数组结构,因此具有固定容量,并且支持公平或非公平锁策略。构造时需要指定容量大小,一旦创建后无法更改。如下示例代码创建了一个容量为10的公平锁ArrayBlockingQueue:

ArrayBlockingQueue<String> fairQueue = new ArrayBlockingQueue<>(10true);

该队列在满或者空时,会通过内部维护的notEmpty和notFull条件变量来控制生产者和消费者的阻塞与唤醒。

LinkedBlockingQueue: LinkedBlockingQueue使用链表数据结构,可以设置初始容量(默认值为Integer.MAX_VALUE),意味着如果不指定容量,则它是一个无界队列。此队列遵循先进先出(FIFO)原则。以下是如何创建一个初始容量为20的LinkedBlockingQueue:

LinkedBlockingQueue<Integer> linkedQueue = new LinkedBlockingQueue<>(20);

DelayQueue: DelayQueue中的元素必须实现Delayed接口,每个元素都有一个可延迟的时间,只有当这个延迟时间过期后,消费者才能从队列中取出该元素。这种特性适用于处理定时任务等场景。以下是如何向DelayQueue添加一个延时对象:

class DelayedTask implements Delayed {
    // 实现Delayed接口的方法
}

DelayQueue<DelayedTask> delayQueue = new DelayQueue<>();
delayQueue.put(new DelayedTask(...)); // 填充带有延迟信息的任务

PriorityBlockingQueue: PriorityBlockingQueue是一种无界的优先级队列,元素按照优先级顺序被取出。优先级通过构造函数传入的Comparator决定,若不提供则按元素的自然排序。下面是如何创建并插入一个根据自定义比较器排序的队列:

class Task implements Comparable<Task{
    int priority;
    // 实现Comparable接口的方法
}

Comparator<Task> comparator = Comparator.comparing(Task::getPriority);
PriorityBlockingQueue<Task> priorityQueue = new PriorityBlockingQueue<>(10, comparator);
priorityQueue.put(new Task(...));

SynchronousQueue: SynchronousQueue是一种特殊的阻塞队列,它没有内部容量,始终要求生产和消费操作完全匹配:每个put操作都需要有对应的take操作同时发生,反之亦然。对于希望直接传递对象而不进行存储的场景非常有用。下面是SynchronousQueue的基本用法:

SynchronousQueue<Integer> syncQueue = new SynchronousQueue<>();
Thread producerThread = new Thread(() -> {
    try {
        syncQueue.put(1); // 这里将一直阻塞,直到有消费者线程调用take()
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});
Thread consumerThread = new Thread(() -> {
    try {
        Integer value = syncQueue.take(); // 这里将一直阻塞,直到有生产者线程调用put()
        System.out.println("Consumed: " + value);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});

producerThread.start();
consumerThread.start();

总之,不同类型的阻塞队列设计各异,开发者应根据实际应用场景选择合适的阻塞队列实现,以充分利用它们各自的优势,确保多线程环境下的高效、安全同步。

阻塞队列的原理剖析


阻塞队列的原理剖析主要围绕其如何利用Java并发包中的锁和条件变量机制来实现线程间的高效同步。以ArrayBlockingQueue为例,其内部使用了ReentrantLock以及两个Condition对象notEmpty和notFull来进行生产和消费过程的控制。

锁(ReentrantLock)的作用 在ArrayBlockingQueue中,所有对共享资源的操作都被保护在一个ReentrantLock之内,确保同一时间只有一个线程能够执行put或take操作。例如,当一个生产者线程试图向满的队列中添加元素时,它必须首先获取到lock锁,否则将被阻塞在外等待。

final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 获取锁,支持中断

条件变量(Condition)的运用

  • notEmpty:当队列为空时,消费者线程调用take()方法会阻塞并注册到notEmpty条件上,直到有生产者线程put了一个新元素进入队列,并通过notEmpty.signal()唤醒消费者线程继续执行。
  • notFull:反之,当队列已满时,生产者线程调用put()方法会被阻塞并注册到notFull条件上,直到有消费者线程从队列中取走一个元素,使得队列不满,然后通过notFull.signal()唤醒生产者线程继续插入元素。
while (count == items.length) { // 判断队列是否已满
    notFull.await(); // 生产者线程在此阻塞等待
}
enqueue(e); // 添加元素至队列

// 对于消费者线程:
while (count == 0) { // 判断队列是否为空
    notEmpty.await(); // 消费者线程在此阻塞等待
}
return dequeue(); // 从队列移除并返回一个元素

put与take操作流程详解

  • put(E e)方法:生产者线程首先尝试获取锁,如果成功则检查队列是否已满,未满则直接加入元素并唤醒一个等待的消费者线程;若队列已满,则当前线程会在notFull条件上等待,直至其他线程消费元素后释放空间。
  • take()方法:消费者线程同样先尝试获取锁,如果成功则检查队列是否为空,不为空则立即移除并返回一个元素,并唤醒一个等待的生产者线程;若队列为空,则当前线程在notEmpty条件上等待,直至其他线程放入元素后提供可消费的数据。

总结来说,阻塞队列通过巧妙地结合ReentrantLock及其内部的多个Condition对象实现了线程间的协作与同步,确保了生产者线程在队列未满时可以顺利地添加元素,而消费者线程则在队列非空时能及时消费元素。这种设计避免了线程间的无效竞争和资源浪费,保证了多线程环境下的数据一致性及程序性能。

阻塞队列的应用实例与场景


阻塞队列在多线程编程中具有广泛的应用,特别是在生产者-消费者模式、任务调度以及线程池管理等场景中扮演着至关重要的角色。

生产者-消费者模型实例与分析 在一个典型的生产者-消费者场景中,我们可以使用ArrayBlockingQueue来实现两个线程间的同步交互。下面是一个简化的示例代码:

import java.util.concurrent.ArrayBlockingQueue;

public class Test {
    private static final int QUEUE_CAPACITY = 10;
    private final ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        Thread producer = new Thread(test.new Producer());
        Thread consumer = new Thread(test.new Consumer());

        producer.start();
        consumer.start();

        producer.join();
        consumer.join();
    }

    class Producer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                try {
                    queue.put(i);
                    System.out.println("生产者插入了一个元素:" + i + ",队列剩余空间:" + (QUEUE_CAPACITY - queue.size()));
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    class Consumer implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Integer item = queue.take();
                    System.out.println("消费者消费了一个元素:" + item + ",当前队列大小:" + queue.size());
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }
}

在这个例子中,生产者线程持续地向ArrayBlockingQueue中添加整数,当队列满时,put操作会自动阻塞;而消费者线程则不断从队列中移除并打印元素,当队列为空时,take操作也会被阻塞。通过这种方式,阻塞队列成功协调了两个线程的执行节奏,避免了资源竞争和数据不一致的问题。

线程池中的应用 Java线程池(ThreadPoolExecutor)是另一个利用阻塞队列作为核心组件的典型例子。在创建线程池时,可以指定一个BlockingQueue作为工作队列,用于存储待执行的任务。当核心线程忙碌或超出其最大容量时,新提交的任务会被放入此队列中等待执行。如下所示:

import java.util.concurrent.*;

public class ThreadPoolExample {
    public static void main(String[] args) {
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            5// 核心线程数
            10// 最大线程数
            60// 空闲线程存活时间
            TimeUnit.SECONDS,
            workQueue // 使用LinkedBlockingQueue作为工作队列
        );

        // 提交多个任务到线程池
        for (int i = 0; i < 20; i++) {
            executor.execute(() -> {
                // 执行具体任务逻辑
                System.out.println("正在执行任务:" + Thread.currentThread().getName());
            });
        }

        // 关闭线程池
        executor.shutdown();
    }
}

在上述代码中,ThreadPoolExecutor内部的工作机制正是依赖于阻塞队列对任务进行缓存和分配。当线程池无法立即处理所有提交的任务时,新的任务会被放入LinkedBlockingQueue中排队等待,直到有空闲的线程可用。这种设计极大地提高了系统处理并发任务的能力,并保证了线程资源的有效利用。

总结


在深入浅出Java多线程之阻塞队列的学习过程中,我们已经了解到阻塞队列作为Java并发编程中的重要工具,它不仅简化了生产者-消费者模式的实现,还有效地解决了线程间同步问题。通过ArrayBlockingQueue、LinkedBlockingQueue、DelayQueue、PriorityBlockingQueue和SynchronousQueue等不同的实现类,我们可以根据实际需求选择适合的阻塞队列类型,以确保线程安全地存储和传递数据。

回顾本篇文档中给出的实例,我们可以看到阻塞队列在多线程环境下的高效运作机制,比如在生产者-消费者模型中,生产者线程使用put()方法将元素放入队列,并在队列满时被阻塞;而消费者线程利用take()方法从队列中取出元素,在队列空时也被相应地阻塞。这种设计使得系统无需显式处理复杂的等待-通知逻辑,极大地提高了程序开发效率和系统的稳定性。

此外,阻塞队列还在Java线程池(ThreadPoolExecutor)中扮演着核心角色,作为任务缓冲区,保证了线程资源的有效分配和调度。例如,当新任务提交到已饱和的线程池时,任务会被暂存于工作队列中,如LinkedBlockingQueue,等待线程执行完成后再从队列中取出并执行。

总结来说,阻塞队列是Java并发编程的核心组件之一,熟练运用它可以更好地解决多线程间的同步问题,提高系统整体性能。文章来源地址https://www.toymoban.com/news/detail-841689.html

到了这里,关于深入浅出Java多线程(十三):阻塞队列的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深入浅出Java的多线程编程——第二篇

    目录 前情回顾 1. 中断一个线程 1.1 中断的API 1.2 小结 2. 等待一个线程  2.1 等待的API 3. 线程的状态 3.1 贯彻线程的所有状态 3.2 线程状态和状态转移的意义 4. 多线程带来的的风险-线程安全 (重点) 4.1 观察线程不安全 4.2 线程安全的概念 4.3 线程不安全的原因 4.3.1 修改共享数据

    2024年02月07日
    浏览(81)
  • 深入浅出RabbitMQ:顺序消费、死信队列和延时队列

    大家好,我是小❤,一个漂泊江湖多年的 985 非科班程序员,曾混迹于国企、互联网大厂和创业公司的后台开发攻城狮。 上篇文章(应对流量高峰的利器——消息中间件)中,我们已经介绍了消息中间件的用途,主要用作:解耦、削峰、异步通信、应用解耦,并介绍了业界常

    2024年02月03日
    浏览(37)
  • 深入浅出线程池

    线程 (thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际 运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线 程并行执行不同的任务。 既然我们创建了线程,那为何我们直接调用方法和我们调

    2024年02月08日
    浏览(50)
  • 深入浅出C++ ——线程库

      在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了 原子类 的概念。要使用标准库中

    2024年02月03日
    浏览(70)
  • 【深入浅出 Spring Security(十三)】使用 JWT 进行前后端分离认证(附源码)

    JWT 全称 Java web Token,在此所讲述的是 JWT 用于身份认证,用服务器端生成的JWT去替代原始的Session认证,以提高安全性。 JWT本质是一个Token令牌,是由三部分组成的字符串,分别是头部(header)、载荷(payload)和签名(signature)。头部一般包含该 JWT 的基本信息,例如所使用的

    2024年02月12日
    浏览(42)
  • 深入浅出带你玩转栈与队列——【数据结构】

    W...Y的主页 😊 代码仓库分享 💕 目录 1.栈 1.1栈的概念及结构 1.2栈的结构特征图  ​编辑 1.3栈的实现 1.3.1栈的初始化 1.3.2进栈 1.3.3出栈 1.3.4销毁内存 1.3.5判断栈是否为空 1.3.5栈底元素的读取 1.3.6栈中大小 1.4栈实现所有接口 2.队列 2.1队列的概念 2.2队列的结构   2.3队列的实

    2024年02月11日
    浏览(62)
  • AIGC内容分享(五十三):AIGC|深入浅出一个完整的视频生成系统「VGen」核心基础知识

    目录 一、VGen整体架构 二、VGen核心基础内容 三、快速上手使用VGen进行视频生成 四、VGen与SVD的比较 「VGen」 是一个基于扩散模型的视频生成系统,提供以视频生成扩散模型为中心的强大代码库,具有先进的视频生成模型。VGen的整体架构主要围绕三个关键领域: 基本模型、创

    2024年01月21日
    浏览(67)
  • 【深入浅出C#】章节 9: C#高级主题:多线程编程和并发处理

    多线程编程和并发处理的重要性和背景 在计算机科学领域,多线程编程和并发处理是一种关键技术,旨在充分利用现代计算机系统中的多核处理器和多任务能力。随着计算机硬件的发展,单一的中央处理单元(CPU)已经不再是主流,取而代之的是多核处理器,这使得同时执行

    2024年02月11日
    浏览(58)
  • 【昕宝爸爸小模块】深入浅出之JDK21 中的虚拟线程到底是怎么回事(一)

    ➡️博客首页       https://blog.csdn.net/Java_Yangxiaoyuan        欢迎优秀的你👍点赞、🗂️收藏、加❤️关注哦。        本文章CSDN首发,欢迎转载,要注明出处哦!        先感谢优秀的你能认真的看完本文,有问题欢迎评论区交流,都会认真回复! 虚拟线程这个

    2024年01月16日
    浏览(49)
  • 【昕宝爸爸小模块】深入浅出之JDK21 中的虚拟线程到底是怎么回事(二)

    ➡️博客首页       https://blog.csdn.net/Java_Yangxiaoyuan        欢迎优秀的你👍点赞、🗂️收藏、加❤️关注哦。        本文章CSDN首发,欢迎转载,要注明出处哦!        先感谢优秀的你能认真的看完本文,有问题欢迎评论区交流,都会认真回复! 上一篇博文:

    2024年01月16日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包