Java 线程详解 --线程概念、线程池、线程同步与安全机制

一、Java线程的概念

Java 线程的本质:每个线程对应一个操作系统线程,由操作系统调度。JVM 通过调用操作系统 API(如 Linux 的 pthread)创建线程。

关键点
用户态与内核态:线程调度依赖操作系统(内核级线程),能直接利用多核 CPU。
线程生命周期:新建 → 就绪 → 运行 → 阻塞 → 终止。
JVM 线程模型:每个 Java 线程对应一个 Thread 对象,通过 start() 触发操作系统线程的创建。


二、线程的使用方法

方式 1:继承 Thread 类(简单但不够灵活)
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程执行: " + Thread.currentThread().getName());
    }
}

// 使用
MyThread t = new MyThread();
t.start(); // 注意:必须调用 start(),而不是直接 run()
方式 2:实现 Runnable 接口(推荐,避免单继承限制)
class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println("任务执行: " + Thread.currentThread().getName());
    }
}

// 使用
Thread t = new Thread(new MyTask());
t.start();
方式 3:带返回值的 Callable(适合需要结果的任务)

Callable:带返回值的任务(像“下单”),比如 Callable<String> 表示这个任务最终会返回一个 String
Future:代表异步任务的“凭证”(像“订餐小票”),凭它未来可以取结果。

以订餐流程举例:

  1. 提交任务:你去餐厅点了一份炒饭,服务员给你一张小票Future)。
  2. 后厨做菜:厨师(线程池中的线程)开始炒饭(执行 Callable)。
  3. 等待结果:你可以干其他事情(不阻塞主线程),也可以随时拿小票问:“好了没?”(future.isDone())。
  4. 取回结果:当炒饭做好后,凭小票取餐(future.get() 拿到返回值)。

Future底层实现原理:

  1. 状态跟踪Future 内部维护任务状态:
    未完成:任务还在执行。
    已完成:任务正常结束,保存返回值。
    已取消:任务被中断。
    异常结束:保存抛出的异常。

  2. 阻塞获取:当调用 future.get() 时:
    • 如果任务已完成 → 直接返回结果。
    • 如果未完成 → 当前线程阻塞等待,直到任务完成(内部通过 wait/notify 机制实现)。

  3. 结果存储:任务完成后,返回值(或异常)会被存入 Future 内部的成员变量,供后续读取。

代码示例:

import java.util.concurrent.*;

public class FutureExample {
    public static void main(String[] args) throws Exception {
        // 1. 创建线程池(后厨)
        ExecutorService executor = Executors.newSingleThreadExecutor();

        // 2. 提交 Callable 任务(下单炒饭)
        Future<String> future = executor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(2000); // 模拟炒饭需要2秒
                return "扬州炒饭做好了!";
            }
        });

        System.out.println("提交任务后,主线程继续做其他事情...");

        // 3. 检查是否完成(非阻塞)
        if (future.isDone()) {
            System.out.println("任务已经完成!");
        } else {
            System.out.println("任务还在进行中...");
        }

        // 4. 阻塞获取结果(类似等待取餐)
        String result = future.get(); // 这里会阻塞,直到任务完成
        System.out.println("取到结果:" + result);

        executor.shutdown(); // 关闭线程池(后厨下班)
    }
}

输出:

提交任务后,主线程继续做其他事情...
任务还在进行中...
(等待2秒后)
取到结果:扬州炒饭做好了!
  1. 异常处理
    如果任务中抛出异常,future.get() 会抛出 ExecutionException,可通过 getCause() 获取原始异常:
try {
    future.get();
} catch (ExecutionException e) {
    System.out.println("任务出错:" + e.getCause());
}
  1. 超时控制
    避免无限等待,可以设置超时时间:
String result = future.get(3, TimeUnit.SECONDS); // 最多等3秒
  1. 取消任务
    如果不想等了,可以取消任务:
future.cancel(true); // true表示尝试中断正在执行的任务

三、Java 线程池机制详解

线程池的核心思想

复用线程:避免频繁创建/销毁线程的开销(类似餐厅固定几个服务员服务所有顾客,而不是每来一个顾客就雇佣新服务员)。
资源管控:通过队列缓冲任务,防止系统过载(类似餐厅的等候区,避免人太多挤爆店面)。


线程池的四大核心参数
ThreadPoolExecutor(
    int corePoolSize,      // 核心线程数(常驻员工)
    int maximumPoolSize,   // 最大线程数(临时工上限)
    long keepAliveTime,    // 空闲线程存活时间(临时工多久没活就解雇)
    TimeUnit unit,         // 时间单位
    BlockingQueue<Runnable> workQueue, // 任务队列(等候区座位数)
    RejectedExecutionHandler handler   // 拒绝策略(人满时怎么处理新顾客)
)

参数详解:

  1. corePoolSize:核心线程即使空闲也不会被销毁(除非设置 allowCoreThreadTimeOut)。
  2. maximumPoolSize:当队列满时,允许创建的最大线程数(核心线程 + 临时线程)。
  3. workQueue:常用队列类型:
    ArrayBlockingQueue:有界队列(固定容量)。
    LinkedBlockingQueue:无界队列(默认 Integer.MAX_VALUE,慎用易内存溢出)。
    SynchronousQueue:不存储任务,直接移交(适合瞬时高并发)。
  4. 拒绝策略(当队列和线程池全满时):
    AbortPolicy:抛异常(默认)。
    CallerRunsPolicy:让提交任务的线程自己执行。
    DiscardPolicy:默默丢弃新任务。
    DiscardOldestPolicy:丢弃队列最旧的任务,再尝试提交。

合理设置线程数
CPU密集型:线程数 ≈ CPU核数(避免过多上下文切换)。
IO密集型:线程数 ≈ CPU核数 * 2(或更高,因线程常阻塞在IO)。

监控线程池状态

// 查看活跃线程数
int activeCount = executor.getActiveCount(); 
// 查看任务队列大小
int queueSize = executor.getQueue().size();

线程池的工作流程
                ↗ 核心线程有空 → 立即执行
任务提交 → 检查核心线程
                ↘ 核心线程忙 → 入队列 → 队列满? → 否 → 等待
                                      ↘ 是 → 创建临时线程 → 超过最大数? → 是 → 拒绝

常用线程池(通过 Executors 工厂创建)
1. FixedThreadPool(固定大小团队)
ExecutorService fixedPool = Executors.newFixedThreadPool(4); // 4个核心线程

特点:核心线程=最大线程,无临时线程,使用无界队列(LinkedBlockingQueue)。
适用场景:已知并发量且任务耗时较长(如后台计算)。

2. CachedThreadPool(弹性团队)
ExecutorService cachedPool = Executors.newCachedThreadPool(); 

特点:核心线程=0,最大线程=Integer.MAX_VALUE,空闲线程60秒回收,使用 SynchronousQueue(直接移交任务)。
适用场景:短时高频小任务(如HTTP请求处理)。

3. SingleThreadExecutor(单人团队)
ExecutorService singlePool = Executors.newSingleThreadExecutor();

特点:核心线程=最大线程=1,无界队列,保证任务顺序执行。
适用场景:需要顺序执行的任务(如日志写入)。

4. ScheduledThreadPool(计划任务团队)
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2);
// 延迟3秒执行
scheduledPool.schedule(() -> System.out.println("Run after 3s"), 3, TimeUnit.SECONDS);
// 固定频率执行(每隔1秒)
scheduledPool.scheduleAtFixedRate(() -> System.out.println("Run every 1s"), 0, 1, TimeUnit.SECONDS);

特点:支持定时、周期性任务。
适用场景:心跳检测、定时数据同步。


5. 自定义线程池
public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 创建线程池:2核心线程,5最大线程,10容量队列,拒绝策略抛异常
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2, 
            5, 
            60, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(10),
            new ThreadPoolExecutor.AbortPolicy()
        );

        // 提交20个任务(测试队列满时的扩容)
        for (int i = 0; i < 20; i++) {
            final int taskId = i;
            try {
                executor.submit(() -> {
                    System.out.println("任务 " + taskId + " 由线程 " + Thread.currentThread().getName() + " 执行");
                    try { Thread.sleep(1000); } catch (InterruptedException e) {}
                });
            } catch (RejectedExecutionException e) {
                System.out.println("任务 " + taskId + " 被拒绝!");
            }
        }

        executor.shutdown(); // 平滑关闭(等待已有任务完成)
    }
}

输出分析
• 前2个任务由核心线程立即执行。
• 接下来的10个任务进入队列。
• 当队列满后(10个),创建3个临时线程(总线程数=5)。
• 第18个任务提交时,线程数已达最大(5),队列满(10),触发拒绝策略抛异常。

场景推荐线程池参数要点
长期稳定并发任务FixedThreadPool核心线程数=最大线程数,队列容量适中
短期突发小任务CachedThreadPool注意防止无限创建线程(适合可控的短任务)
单线程顺序执行SingleThreadExecutor替代手动创建线程,保证顺序性
定时/周期性任务ScheduledThreadPool指定延迟和周期
高并发自定义需求ThreadPoolExecutor根据业务特点调整核心参数

线程池关闭方法
  1. shutdown():平滑关闭,不再接受新任务,等待已有任务完成。
  2. shutdownNow():立刻停止所有任务,返回未执行的任务列表。
List<Runnable> unfinishedTasks = executor.shutdownNow();

四、线程同步与安全,原理、用法及示例

在多线程环境下,当多个线程同时访问共享资源(如变量、文件、数据库)时,可能导致数据不一致或逻辑错误。线程同步的目的是协调线程间的执行顺序,确保线程安全。

详细参考:https://blog.csdn.net/gengzhikui1992/article/details/147230900?spm=1001.2014.3001.5501

常见同步方式及底层原理
1. synchronized 关键字:

原理:基于对象的内置锁(Monitor),每个对象关联一个Monitor。
执行synchronized代码时,线程需获取对象的Monitor锁:
• 成功则持有锁,执行代码。
• 失败时线程进入锁的等待队列(EntrySet),阻塞等待唤醒。
用法

// 同步方法
public synchronized void safeMethod() { /* ... */ }

// 同步代码块
public void someMethod() {
    synchronized (this) { // 锁对象为当前实例
        // 临界区代码
    }
}

示例

class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++; // 原子操作
    }
}

解析synchronized确保同一时刻仅一个线程执行increment()方法。


2. ReentrantLock 可重入锁:

原理:基于AQS(AbstractQueuedSynchronizer),维护一个CLH队列管理等待线程。
支持可重入性(同一线程可多次加锁)和公平性(可选)。
用法

private final ReentrantLock lock = new ReentrantLock();

public void safeMethod() {
    lock.lock();  // 手动加锁
    try {
        // 临界区代码
    } finally {
        lock.unlock(); // 必须手动释放
    }
}

示例(带超时):

if (lock.tryLock(1, TimeUnit.SECONDS)) { // 尝试1秒内获取锁
    try { /* ... */ } 
    finally { lock.unlock(); }
} else { // 超时处理 }

优势:比synchronized更灵活,支持尝试锁、公平锁等。

特性synchronizedReentrantLock
锁的获取方式自动获取和释放(JVM管理)手动 lock()unlock()(需写finally)
可中断性不支持(阻塞时无法中断)支持 lockInterruptibly()
超时机制不支持(只能阻塞等待)支持 tryLock(timeout)
公平锁仅非公平锁支持公平和非公平(构造参数控制)
条件变量只能绑定一个条件(wait()/notify()可创建多个条件(newCondition()
性能JDK6后优化后性能接近高并发竞争时性能更好
代码复杂度简单(自动管理)复杂(需手动释放,易忘)
锁的可见性通过JVM内存模型保证基于AQS的volatile变量保证

synchronized 的优势:简单、安全、自动释放锁,适合快速开发。
ReentrantLock 的优势:灵活、功能强大,适合需要超时、中断、公平锁等复杂场景。

最终建议:优先用 synchronized,遇到它无法满足需求时再考虑 ReentrantLock


3. volatile 变量:

多线程环境下,变量操作可能引发两种问题:

  1. 可见性问题:A线程修改了变量,B线程看不到最新值。
  2. 原子性问题:看似一步的操作(如 i++),实际分三步(读-改-写),中间可能被其他线程打断。

volatile 变量:解决了可见性问题,原理:通过内存屏障禁止指令重排序,确保变量的修改对所有线程立即可见(不保证原子性)。

强制读写主内存volatile 变量修改后,其他线程立即可见。
禁止指令重排序:确保代码执行顺序符合预期。

使用场景
适合做 状态标志(如开关控制),不涉及复杂计算。

public class Server {
    private volatile boolean isRunning = true;  // 状态标志

    public void stop() {
        isRunning = false;  // 修改后,其他线程立即可见
    }

    public void run() {
        while (isRunning) {  // 循环读取最新值
            // 处理请求...
        }
    }
}

局限性
不保证原子性volatile 无法解决 i++ 这种非原子操作的问题。

volatile int count = 0;
count++;  // 实际分三步:读 -> 改 -> 写(线程不安全!)

4. 原子类(Atomic Classes):解决原子性问题

封装原子操作:通过 CPU 的 CAS(Compare-And-Swap) 指令,保证操作的原子性。
无需加锁:性能优于 synchronized

常见类
AtomicIntegerAtomicLong:整型原子操作。
AtomicReference:对象引用原子操作。
AtomicStampedReference:解决 ABA 问题(版本号控制)。

使用场景
适合 计数器累加器 等需要原子操作的场景。

public class Counter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();  // 原子自增
    }

    public int get() {
        return count.get();
    }
}

示例:AtomicReference 保证对象引用原子性

public class AtomicReferenceDemo {
    private AtomicReference<String> latestMessage = new AtomicReference<>("");

    public void updateMessage(String message) {
        latestMessage.set(message);  // 原子更新引用
    }

    public String getMessage() {
        return latestMessage.get();
    }
}

示例:解决 ABA 问题(AtomicStampedReference)

public class ABADemo {
    private AtomicStampedReference<Integer> atomicValue = 
        new AtomicStampedReference<>(100, 0);  // 初始值100,版本号0

    public void update() {
        int[] stampHolder = new int[1];
        int currentValue = atomicValue.get(stampHolder);  // 获取值和版本号
        int newValue = currentValue + 1;
        atomicValue.compareAndSet(currentValue, newValue, stampHolder[0], stampHolder[0] + 1);
    }
}
特性volatile原子类(如 AtomicInteger)
解决可见性问题✔️(强制主内存读写)✔️(内部使用 volatile 变量)
解决原子性问题❌(如 i++ 仍不安全)✔️(封装原子操作)
性能高(无锁)高(CAS 无锁)
适用场景状态标志、开关控制计数器、累加器、复杂原子操作
ABA 问题无法解决可通过 AtomicStampedReference 解决

volatile 是轻量级的可见性解决方案,不保证原子性
• 原子类通过 CAS 实现无锁原子操作,同时解决可见性和原子性
• 两者性能均优于锁,但适用场景不同,不要混淆!

  1. volatile
    • 变量被多个线程共享,但只有一个线程修改它。
    • 需要立即可见性,但不涉及复合操作(如 i++)。
    • 典型场景:状态标志(boolean 开关)。

  2. 用原子类
    • 变量被多个线程频繁修改(如计数器)。
    • 需要原子性操作(如 addAndGet()compareAndSet())。
    • 典型场景:并发计数器、无锁数据结构。

  3. synchronizedReentrantLock
    • 需要同步多步操作(如先读后写)。
    • 原子类和 volatile 无法满足复杂逻辑时。


6. 读写锁 ReentrantReadWriteLock

想象一个图书馆:
读锁:允许多个人同时读书(共享资源)。
写锁:当有人要修改书的内容时,必须清场(独占资源),其他人不能读也不能写。

核心规则

  1. 读锁之间不互斥:多个线程可以同时持有读锁。
  2. 写锁与其他锁互斥:写锁生效时,其他线程不能获取读锁或写锁。
  3. 写锁优先:如果写锁在等待,新来的读锁会被阻塞(防止“写线程饥饿”)。

为什么用读写锁?
读多写少 的场景下(如缓存系统、配置管理),用读写锁比普通互斥锁(如 synchronized)性能更高:
读操作:允许多线程并发读取。
写操作:保证独占修改,避免数据不一致。

场景:实现一个线程安全的缓存系统

public class Cache<K, V> {
    private final Map<K, V> cacheMap = new HashMap<>();
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();   // 读锁
    private final Lock writeLock = rwLock.writeLock(); // 写锁

    // 读操作:允许多线程并发读
    public V get(K key) {
        readLock.lock();
        try {
            return cacheMap.get(key);
        } finally {
            readLock.unlock();
        }
    }

    // 写操作:独占访问
    public void put(K key, V value) {
        writeLock.lock();
        try {
            cacheMap.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }

    // 复杂操作:先读后写(需要先释放读锁,再获取写锁)
    public void updateIfPresent(K key, V newValue) {
        readLock.lock();
        try {
            if (cacheMap.containsKey(key)) {
                // 释放读锁,获取写锁(注意:不能直接升级锁!)
                readLock.unlock();
                writeLock.lock();
                try {
                    cacheMap.put(key, newValue);
                } finally {
                    writeLock.unlock();
                }
                // 重新获取读锁(如果需要)
                readLock.lock();
            }
        } finally {
            readLock.unlock();
        }
    }
}

关键细节

  1. 避免锁升级:先释放读锁,再获取写锁。
  2. 写锁优先:如果写锁在等待,后续读锁会被阻塞(可通过公平锁缓解)。
  3. 锁的释放:必须用 try-finally 确保释放,否则会导致死锁。

在持有写锁时,可以获取读锁,然后释放写锁(保持数据可见性):

public void writeThenRead() {
    writeLock.lock();
    try {
        // 修改数据...
        readLock.lock();  // 锁降级(允许)
    } finally {
        writeLock.unlock();
    }

    try {
        // 读取数据...
    } finally {
        readLock.unlock();
    }
}

不能直接从读锁升级到写锁(会导致死锁):

public void readThenWrite() {
    readLock.lock();
    try {
        // 如果发现需要修改数据...
        writeLock.lock();  // 错误!会阻塞,因为读锁未释放,其他线程也无法释放写锁
    } finally {
        readLock.unlock();
    }
}

线程同步方式的对比与选择
方式原理适用场景性能
synchronized对象内置锁简单同步,少量竞争中等,自动释放锁
ReentrantLockAQS队列锁复杂控制(如超时、公平锁)高,需手动释放
volatile内存可见性单变量状态标志极高,无锁
原子类CAS指令计数器,单变量原子操作高,无锁竞争
ReentrantReadWriteLock读写分离锁读多写少场景读高,写中等
  1. 优先选择高级工具:如java.util.concurrent包下的并发集合(ConcurrentHashMap, BlockingQueue)。
  2. 避免锁粒度过大:尽量缩小同步范围,减少竞争。
  3. 资源释放:使用Lock时务必在finally中释放锁。
  4. 分工协作:读写分离时使用ReentrantReadWriteLock提升性能。
  5. 监测工具:利用jstackVisualVM排查死锁和性能瓶颈。

五、Java线程安全集合详解

Java提供了多种线程安全的集合类,适用于高并发场景。它们通过内部优化(如分段锁、写时复制)实现高效并发,避免开发者手动加锁。以下是常见线程安全集合及其使用场景和示例:


1. ConcurrentHashMap(并发哈希表)

原理:将数据分为多个段(Segment,Java 8后改为Node数组+CAS),每个段独立加锁。允许多线程同时读写不同段的数据。
适用场景:高并发键值存储(如缓存、计数器)。
示例

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

// 线程安全的插入(若key不存在)
map.putIfAbsent("apple", 1);

// 原子累加操作
map.compute("apple", (k, v) -> v == null ? 1 : v + 1);

// 遍历(弱一致性迭代器,不抛ConcurrentModificationException)
map.forEach((k, v) -> System.out.println(k + ": " + v));

2. CopyOnWriteArrayList(写时复制列表)

原理:写操作时复制整个数组(加锁保证原子性),读操作无锁。适合读多写少的场景。
适用场景:监听器列表、配置信息列表。
示例

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("user1");

// 读操作无需加锁
for (String user : list) {
    System.out.println(user);
}

// 写操作会复制新数组
list.add("user2"); // 原数组:[user1],新数组:[user1, user2]

3. BlockingQueue(阻塞队列)

原理:当队列空时阻塞消费者,队列满时阻塞生产者。内部通过ReentrantLockCondition实现。
常见实现
ArrayBlockingQueue:有界队列(数组实现)。
LinkedBlockingQueue:可选有界或无界(链表实现)。
PriorityBlockingQueue:优先级阻塞队列。
适用场景:生产者-消费者模型。
示例

BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);

// 生产者
new Thread(() -> {
    try {
        queue.put("task1"); // 队列满时阻塞
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

// 消费者
new Thread(() -> {
    try {
        String task = queue.take(); // 队列空时阻塞
        System.out.println("处理任务: " + task);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

4. ConcurrentLinkedQueue(并发链表队列)

原理:基于无锁算法(CAS),实现非阻塞线程安全队列。
适用场景:高并发非阻塞队列(如任务分发)。
示例

ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();

// 生产者
queue.offer(100); // 非阻塞添加

// 消费者
Integer value = queue.poll(); // 非阻塞取出

5. ConcurrentSkipListMap(并发跳表映射)

原理:基于跳表(Skip List)数据结构,实现有序的并发Map。
适用场景:需要排序的高并发键值存储(如排行榜)。
示例

ConcurrentSkipListMap<Integer, String> rank = new ConcurrentSkipListMap<>();
rank.put(90, "Alice");
rank.put(85, "Bob");

// 按分数从高到低遍历
rank.descendingMap().forEach((score, name) -> {
    System.out.println(name + ": " + score);
});

6. CopyOnWriteArraySet(写时复制集合)

原理:基于CopyOnWriteArrayList实现,用数组存储元素,写操作时复制。
适用场景:读多写少的集合(如IP白名单)。
示例

CopyOnWriteArraySet<String> ips = new CopyOnWriteArraySet<>();
ips.add("192.168.1.1");

// 检查IP是否存在(无需加锁)
if (ips.contains("192.168.1.1")) {
    System.out.println("IP允许访问");
}

7. Collections.synchronizedXXX(同步包装类)

原理:通过包装普通集合,对所有方法加synchronized锁。
适用场景:低并发环境(性能低于专用并发集合)。
示例

List<String> syncList = Collections.synchronizedList(new ArrayList<>());

// 遍历时需手动加锁
synchronized (syncList) {
    for (String item : syncList) {
        System.out.println(item);
    }
}

线程安全集合对比表
集合名称原理适用场景性能特点
ConcurrentHashMap分段锁/CAS高并发键值存储高吞吐量,低延迟
CopyOnWriteArrayList写时复制读多写少的列表写操作慢,读操作快
BlockingQueue锁+条件队列生产者-消费者模型阻塞操作,适用于任务调度
ConcurrentLinkedQueue无锁(CAS)高并发非阻塞队列高并发,无锁
ConcurrentSkipListMap跳表有序并发键值存储查询和插入O(log n)
CopyOnWriteArraySet基于CopyOnWriteArrayList读多写少的集合CopyOnWriteArrayList
  1. 键值存储
    • 高并发写入:ConcurrentHashMap
    • 需要排序:ConcurrentSkipListMap

  2. 列表/集合
    • 读多写少:CopyOnWriteArrayList/CopyOnWriteArraySet
    • 写操作频繁:使用ConcurrentHashMap模拟(如Collections.newSetFromMap

  3. 队列
    • 阻塞队列:ArrayBlockingQueue/LinkedBlockingQueue
    • 非阻塞队列:ConcurrentLinkedQueue

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值