
引言在 Java 多线程编程中Java.util.concurrent 包简称 JUC提供了一系列线程安全、高性能的工具类解决了传统多线程开发中线程同步、任务调度、并发容器等核心问题。本文将围绕 JUC 中常用的核心组件Callable 接口、ReentrantLock、信号量、CountDownLatch 及并发集合展开解析并结合代码示例说明其用法与设计思想。一、先搞懂JUC到底是什么JUC其实就是Java提供的一个“多线程工具包”里面封装了各种现成的工具类不用我们自己手动写复杂的同步逻辑就能轻松实现线程安全、任务调度、并发容器等需求核心作用就是“简化多线程开发提升并发性能”。二、Callable接口带返回值的“任务”我们之前学过Runnable接口它是用来定义线程任务的但有个缺点——run方法没有返回值也不能抛出异常。而Callable接口和Runnable是并列的它解决了这个问题能定义“带返回值”的线程任务。核心区别很简单1. Callable实现call()方法有返回值返回值是泛型可指定类型能抛出异常2. Runnable实现run()方法无返回值不能抛出异常。这里有个注意点Thread的构造方法里不能直接传入Callable对象必须借助一个“中间件”——FutureTask。FutureTask的作用就是“接收Callable的返回值”相当于一个“任务容器”把Callable包装后再传给Thread执行。具体代码示例如下public static void main(String[] args) throws ExecutionException, InterruptedException { // 1. 创建Callable任务定义一个计算1到100和的任务返回值是Integer CallableInteger callable new CallableInteger() { Override public Integer call() throws Exception { int result 0; for (int i 0; i 100; i) { result i; } return result; // 任务的返回值 } }; // 2. 用FutureTask包装Callable目的是接收返回值 FutureTaskInteger task new FutureTask(callable); // 3. 把FutureTask传给Thread启动线程 Thread t new Thread(task); t.start(); // 4. 用get()方法获取返回值重点 // 注意get()会阻塞——如果线程没执行完就一直等直到拿到结果 System.out.println(1到100的和 task.get()); }为什么Thread不能直接接收Callable核心是“解耦合”。Thread的作用只是“启动线程”它不需要关心任务有没有返回值、会不会抛异常把这些细节交给FutureTask处理让Thread更专注于自己的核心职责。三、ReentrantLock更灵活的“可重入锁”ReentrantLock的核心是“可重入”Reentrant就是可重入的意思它和synchronized是并列的锁工具都能解决线程安全问题但ReentrantLock的用法更灵活功能也更强大。先看最基础的用法解决线程安全问题代码示例private static int count 0; public static void main(String[] args) { // 1. 创建ReentrantLock实例 ReentrantLock locker new ReentrantLock(); // 2. 线程1对count自增100次 Thread t1 new Thread(() - { for (int i 0; i 100; i) { locker.lock(); // 加锁 count; locker.unlock(); // 解锁 } }); // 3. 线程2对count自增100次 Thread t2 new Thread(() - { for (int i 0; i 100; i) { locker.lock(); // 加锁 count; locker.unlock(); // 解锁 } }); t1.start(); t2.start(); // 等待两个线程执行完毕 try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(count count); // 最终输出200线程安全 }和synchronized相比ReentrantLock最明显的区别是加锁、解锁需要手动调用lock()和unlock()方法。但这里有个关键细节——如果在unlock()之前程序抛出异常或者提前return这就会导致没进行到解锁操作。解决办法很简单把unlock()放在finally代码块里这样无论程序是否抛出异常都会执行解锁操作示例如下locker.lock(); try { count; // 可能抛出异常的操作 } finally { locker.unlock(); // 确保解锁避免死锁 }ReentrantLock 与 synchronized 的核心区别1.synchronized是一个关键字它的内部实现是在JVM内部通过C代码来完成的而ReentrantLock是Java标准库中的类由Java代码来实现2.synchronized是通过代码块来实现加锁与解锁操作而ReentrantLock需要使用lock/unlock方法同时也需要注意unlock不被调用的问题。3.ReentrantLock还提供了一个trylock()方法这个方法有一个特点就是它不会阻塞当加锁成功就返回true加锁失败就返回false由调用者决定接下来怎么处理。同时这个方法也可以传入一个时间参数这就设置了超时的时间这就可以设置当等待时间超时之后再返回true/false4.ReentrantLock提供了公平锁的实现ReentrantLock lockernew ReentrantLock(true);当我们传入true的时候这把锁就是一把公平锁相反当传入false时这把锁就是一把非公平锁5.ReentrantLock 配套的 Condition 类实现了等待/通知机制其功能比 wait/notify 更为强大。四、Semaphore资源访问控制工具Semaphore信号量用于控制同时访问特定资源的线程数量本质是一个计数器通过 “申请资源P 操作” 和 “释放资源V 操作” 实现资源的协调分配。核心逻辑申请资源调用 acquire ()计数器减 1若计数器为 0线程阻塞等待释放资源调用 release ()计数器加 1唤醒阻塞的线程若有。下面我们用代码来演示一下Semaphore semaphorenew Semaphore(4); semaphore.acquire(); System.out.println(进行一次P操作); semaphore.acquire(); System.out.println(进行一次P操作); semaphore.acquire(); System.out.println(进行一次P操作); semaphore.acquire(); System.out.println(进行一次P操作);当计数器为0的时候如果我们继续申请资源就会阻塞等待Semaphore semaphorenew Semaphore(3); semaphore.acquire(); System.out.println(进行一次P操作); semaphore.acquire(); System.out.println(进行一次P操作); semaphore.acquire(); System.out.println(进行一次P操作); semaphore.acquire(); System.out.println(进行一次P操作);二元信号量模拟锁初始值为 1 的 Semaphore 可模拟锁的效果仅允许一个线程访问资源解决线程安全问题private static int count0; public static void main(String[] args) { Semaphore semaphorenew Semaphore(1); Thread t1new Thread(()-{ for (int i 0; i 100 ; i) { try { semaphore.acquire(); count; semaphore.release(); } catch (InterruptedException e){ e.printStackTrace(); } } }); Thread t2new Thread(()-{ for (int i 0; i 100 ; i) { try { semaphore.acquire(); count; semaphore.release(); } catch (InterruptedException e){ e.printStackTrace(); } } }); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e){ e.printStackTrace(); } System.out.println(count count); }五、CountDownLatch多任务等待工具我们使用多线程的时候经常把一个大的任务拆解成多个子任务然后我们使用多线程执行这些子任务的时候就可以提高程序执行的效率。那么我们如何来衡量多个子任务都完成了呢也就是整个任务都完成了呢这时 CountDownLatch就应运而生了。CountDownLatch latchnew CountDownLatch(10);我们可以像上面这样创建一个CountDownLatch类的实例的时候传入一个参数这表示子任务的个数。当我们执行完一个任务的时候就调用一次countDown()方法。当调用该方法的次数和传入的任务个数相同的时候整个任务就结束了。我们在主线程应当调用await()方法这个方法是等待所有的子任务都结束。下面我们就用代码来演示一下public static void main(String[] args) throws InterruptedException { // 我们现在可以把一个大任务拆成10个子任务 // 接下来把这些任务放到线程池中让线程池来执行 // 构造方法中传入的参数表示任务的个数 CountDownLatch latchnew CountDownLatch(10); ExecutorService executorService Executors.newFixedThreadPool(4); for (int i 0; i 10 ; i) { int idi; executorService.submit(()-{ System.out.println(子任务开始执行 id); System.out.println(子任务结束执行 id); latch.countDown(); }); } //下面这个方法是在等待所有的任务都结束 latch.await(); System.out.println(所有任务都执行完毕); }六、JUC 并发集合线程安全的容器实现在多线程环境下Java 原生集合类如 ArrayList、HashMap并非线程安全直接使用可能导致数据异常需要额外处理以保证并发安全。我们以 ArrayList 为例先梳理常见的三种线程安全处理方式再重点解析 JUC 提供的并发容器。一ArrayList 的三种线程安全处理方式自行加锁:手动分析代码中的原子操作将需要保证线程安全的逻辑包裹在同步块或锁中通过显式控制锁的范围来实现原子性。Collections.synchronizedList(new ArrayList())调用该方法后返回的 List 会对所有核心方法如add、get、remove自动添加synchronized修饰通过粗粒度锁保证线程安全。CopyOnWriteArrayListJUC 提供不依赖传统加锁逻辑采用写时拷贝机制实现线程安全是读多写少场景下的高性能选择。二CopyOnWriteArrayList写时拷贝的线程安全列表核心原理读操作直接访问原数组无需加锁写操作添加 / 修改 / 删除会先拷贝一份原数组在新数组上完成修改后将原数组的引用指向新数组。执行逻辑读操作始终从 “旧数组” 读取数据即使写操作正在进行也不会读到 “修改一半” 的中间状态写操作完成后引用切换后续读操作才能感知到最新数据。优势读操作无锁性能极高读操作与写操作互不阻塞适合读多写少的场景。缺点写操作需要拷贝整个数组数据量较大时性能损耗明显多线程同时写时会产生多次拷贝可能出现数据覆盖问题无法保证数据实时一致性写操作完成前读操作只能看到旧数据。三ConcurrentHashMap高效的线程安全哈希表HashMap 是线程不安全的而Hashtable和ConcurrentHashMap是线程安全的哈希表实现。Hashtable对所有public方法添加全局synchronized锁导致任意两个线程访问任意数据时都会产生锁竞争并发性能极低。而ConcurrentHashMap通过细粒度锁设计大幅提升了并发效率。核心设计锁粒度优化以哈希桶数组的每个元素的头节点作为锁对象仅当多个线程操作同一哈希桶时才会产生锁竞争不同哈希桶的操作可并行执行无需额外空间开销Java 中任意对象均可作为锁。扩容策略采用 “分步扩容”将旧表数据迁移到新表的过程拆分为多次操作每次仅迁移部分数据避免一次性扩容耗时过长导致阻塞。与 Hashtable 的核心区别总结Java.util.concurrentJUC是 Java 多线程编程的核心工具集为开发者提供了一套高效、线程安全的并发解决方案。本文从任务定义、锁机制、资源控制、任务协同到安全容器五个维度系统解析了 JUC 的核心组件Callable FutureTask弥补了 Runnable 无返回值的缺陷实现了带结果的异步任务ReentrantLock提供了比 synchronized 更灵活的锁控制Semaphore以计数器为核心实现了多线程资源访问的限流与协调CountDownLatch解决了多任务并行执行后的等待汇总问题并发集合CopyOnWriteArrayList、ConcurrentHashMap针对不同场景优化了线程安全策略在保证数据一致性的同时最大化并发性能。