😀大家好,我是白晨,一个不是很能熬夜😫,但是也想日更的人✈。如果喜欢这篇文章,点个赞👍,关注一下👀白晨吧!你的支持就是我最大的动力!💪💪💪
Java并发编程基础
并发编程是Java开发中的重要部分,尤其在需要处理多任务、高性能场景时,比如Web服务器、数据处理等。Java的 java.util.concurrent
(简称JUC)工具包提供了丰富的API,帮助开发者管理线程、同步任务和处理并发问题。这篇博客面向初学者,用通俗易懂的语言讲解JUC核心API的用法,同时通过代码示例和流程图帮助你快速上手。
1. 什么是JUC?
JUC是Java 5引入的并发工具包,全称 java.util.concurrent
,包含锁、线程池、阻塞队列等工具,简化多线程编程。简单来说,它是一套帮你控制线程、避免并发问题的利器。
2. 进程与线程
- 进程:运行中的程序,比如打开的浏览器,拥有独立内存空间。
- 线程:进程中的执行单元,一个进程可包含多个线程,比如浏览器加载网页和播放视频的线程。线程是操作系统调度的最小单位。
Java程序默认多线程,比如主线程和垃圾回收线程。JUC帮助我们高效管理这些线程。
3. 核心API及用法
3.1 Thread类和Runnable接口
Thread
类和 Runnable
接口是Java创建和运行线程的基础。Thread
是线程的直接实现,Runnable
是一个任务接口,定义线程要执行的内容。两者结合是多线程编程的起点。
API用法:
- Thread类:
Thread(Runnable)
:构造方法,传入Runnable
任务。start()
:启动线程,调用run
方法。run()
:线程的执行逻辑(通常由Runnable
提供)。join()
:等待线程结束。sleep(long)
:让线程休眠指定毫秒。
- Runnable接口:
run()
:定义线程的任务逻辑,无返回值,不抛检查型异常。
代码示例:
public class ThreadRunnableDemo {
public static void main(String[] args) throws InterruptedException {
// 使用Runnable定义任务
Runnable task = () -> {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " 运行: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 方式1:通过Thread类直接创建线程
Thread t1 = new Thread(task, "线程1");
t1.start();
// 方式2:通过匿名Runnable和Thread
Thread t2 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " 运行: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "线程2");
t2.start();
// 主线程等待子线程结束
t1.join();
t2.join();
System.out.println("主线程:所有线程执行完毕");
}
}
预期输出(线程执行顺序可能不同):
线程1 运行: 0
线程2 运行: 0
线程1 运行: 1
线程2 运行: 1
线程1 运行: 2
线程2 运行: 2
主线程:所有线程执行完毕
解释:
- 定义一个
Runnable
任务,循环打印3次,每次休眠500毫秒。 - 创建两个线程
t1
和t2
,分别执行相同的任务。 start()
启动线程,join()
确保主线程等待子线程完成。- 线程并发运行,输出可能交错,但最终主线程在子线程结束后打印。
流程图:
3.2 synchronized关键字
synchronized
是 Java 提供的基础同步机制,用于确保多线程环境下共享资源的安全访问。它可以应用于方法或代码块,防止多个线程同时修改同一资源导致数据不一致。
API用法:
- synchronized 方法:
- 在方法声明中添加
synchronized
关键字,锁定当前对象(this
)或类(static
方法)。 - 示例:
public synchronized void method()
。
- 在方法声明中添加
- synchronized 代码块:
- 使用
synchronized (obj) {}
锁定指定对象,控制代码块的访问。 - 示例:
synchronized (lockObj) { /* 代码 */ }
。
- 使用
- 注意事项:
- 锁是对象级别的,每个对象有一个锁。
synchronized
是可重入的,同一线程可以多次获取同一锁。- 锁释放是自动的(方法或代码块结束时),无需手动释放。
代码示例:
public class SynchronizedDemo {
private static int count = 0;
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
synchronized (lock) {
count++;
}
}
};
Thread t1 = new Thread(task, "线程1");
Thread t2 = new Thread(task, "线程2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终计数: " + count);
}
}
预期输出:
最终计数: 2000
解释:
- 两个线程各执行1000次
count++
,synchronized
代码块锁定lock
对象,确保同一时间只有一个线程修改count
。 - 最终结果为2000,证明线程安全。
流程图:
补充:synchronized方法示例:
public class SynchronizedMethodDemo {
private static int count = 0;
public static synchronized void increment() {
count++;
}
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
increment();
}
};
Thread t1 = new Thread(task, "线程1");
Thread t2 = new Thread(task, "线程2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终计数: " + count);
}
}
预期输出:
最终计数: 2000
解释:
increment
方法使用synchronized
,锁定SynchronizedMethodDemo.class
(因为是static
方法)。- 效果与代码块示例相同,但更简洁。
3.3 Lock接口
Lock
接口提供比 synchronized
更灵活的锁机制,常用实现是 ReentrantLock
,支持公平锁、超时锁等。
API用法:
lock()
:获取锁,若锁被占用,线程等待。unlock()
:释放锁,需在finally
块中调用。tryLock()
:尝试获取锁,返回是否成功。newCondition()
:创建条件对象,用于线程间通信。
代码示例:
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
private static int count = 0;
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
};
Thread t1 = new Thread(task, "线程1");
Thread t2 = new Thread(task, "线程2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终计数: " + count);
}
}
预期输出:
最终计数: 2000
解释:
- 两个线程各执行1000次
count++
,ReentrantLock
确保线程安全,最终结果为2000。 lock()
和unlock()
保证同一时间只有一个线程修改count
。
流程图:
3.4 线程间通信
线程间协作常通过 Condition
(与 Lock
配合)实现,类似 wait/notify
。
API用法:
Condition.await()
:线程等待,释放锁。Condition.signal()
:唤醒一个等待线程。Condition.signalAll()
:唤醒所有等待线程。
代码示例:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionDemo {
private static final ReentrantLock lock = new ReentrantLock();
private static final Condition condition = lock.newCondition();
private static boolean ready = false;
public static void main(String[] args) {
Thread producer = new Thread(() -> {
lock.lock();
try {
System.out.println("生产者:准备数据...");
Thread.sleep(1000);
ready = true;
condition.signal();
System.out.println("生产者:数据准备完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "生产者");
Thread consumer = new Thread(() -> {
lock.lock();
try {
while (!ready) {
System.out.println("消费者:等待数据...");
condition.await();
}
System.out.println("消费者:数据已收到");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "消费者");
consumer.start();
producer.start();
}
}
预期输出:
消费者:等待数据...
生产者:准备数据...
生产者:数据准备完成
消费者:数据已收到
解释:
- 消费者等待生产者准备数据,
condition.await()
让消费者释放锁并等待。 - 生产者设置
ready = true
后通过condition.signal()
唤醒消费者。
流程图:
3.5 集合的线程安全
JUC提供线程安全的集合,如 ConcurrentHashMap
、CopyOnWriteArrayList
。
API用法(以 ConcurrentHashMap
为例):
put(K, V)
:存储键值对,线程安全。compute(K, BiFunction)
:原子更新键值,适合计数等操作。get(K)
:获取值,线程安全。
代码示例:
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapDemo {
public static void main(String[] args) throws InterruptedException {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
map.compute("key", (k, v) -> v == null ? 1 : v + 1);
}
};
Thread t1 = new Thread(task, "线程1");
Thread t2 = new Thread(task, "线程2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终计数: " + map.get("key"));
}
}
预期输出:
最终计数: 2000
解释:
- 两个线程各执行1000次计数,
compute
方法确保原子性,最终结果为2000。
3.6 Callable接口
Callable
比 Runnable
更灵活,支持返回值和抛出异常。
API用法:
call()
:任务逻辑,返回结果,可抛异常。FutureTask.get()
:获取结果,阻塞直到任务完成。
代码示例:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableDemo {
public static void main(String[] args) throws Exception {
Callable<Integer> callable = () -> {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
};
FutureTask<Integer> task = new FutureTask<>(callable);
new Thread(task).start();
System.out.println("计算结果: " + task.get());
}
}
预期输出:
计算结果: 5050
解释:
Callable
计算1到100的和,返回5050。task.get()
阻塞等待结果。
3.7 JUC三大辅助类
CountDownLatch
等待一组线程完成。
API用法:
countDown()
:计数减1。await()
:等待计数归零。
代码示例:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
Runnable task = () -> {
System.out.println(Thread.currentThread().getName() + " 完成工作");
latch.countDown();
};
new Thread(task, "线程1").start();
new Thread(task, "线程2").start();
new Thread(task, "线程3").start();
latch.await();
System.out.println("所有线程完成,主线程继续");
}
}
预期输出:
线程1 完成工作
线程2 完成工作
线程3 完成工作
所有线程完成,主线程继续
解释:
- 主线程等待三个子线程完成,
latch.await()
阻塞直到计数为0。
CyclicBarrier
让一组线程到达屏障点后同时继续。
API用法:
await()
:等待所有线程到达屏障。- 构造函数可指定到达屏障后的回调任务。
代码示例:
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到达,执行汇总任务");
});
Runnable task = () -> {
System.out.println(Thread.currentThread().getName() + " 到达");
try {
barrier.await();
System.out.println(Thread.currentThread().getName() + " 继续执行");
} catch (Exception e) {
e.printStackTrace();
}
};
new Thread(task, "线程1").start();
new Thread(task, "线程2").start();
new Thread(task, "线程3").start();
}
}
预期输出:
线程1 到达
线程2 到达
线程3 到达
所有线程到达,执行汇总任务
线程1 继续执行
线程2 继续执行
线程3 继续执行
解释:
- 三个线程到达屏障后,执行汇总任务,然后同时继续。
Semaphore
控制并发访问资源的线程数。
API用法:
acquire()
:获取许可,阻塞直到有许可。release()
:释放许可。
代码示例:
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2);
Runnable task = () -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 获取资源");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " 释放资源");
semaphore.release();
}
};
for (int i = 1; i <= 5; i++) {
new Thread(task, "线程" + i).start();
}
}
}
预期输出:
线程1 获取资源
线程2 获取资源
线程1 释放资源
线程3 获取资源
线程2 释放资源
线程4 获取资源
线程3 释放资源
线程5 获取资源
线程4 释放资源
线程5 释放资源
解释:
- 一次最多两个线程获取资源,其他线程等待。
3.8 读写锁
ReentrantReadWriteLock
提供读锁(共享)和写锁(独占),适合读多写少场景。
API用法:
readLock().lock()
:获取读锁,多线程可同时读。writeLock().lock()
:获取写锁,独占。
代码示例:
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
private static int data = 0;
private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args) {
Runnable readTask = () -> {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 读取数据: " + data);
} finally {
lock.readLock().unlock();
}
};
Runnable writeTask = () -> {
lock.writeLock().lock();
try {
data++;
System.out.println(Thread.currentThread().getName() + " 写入数据: " + data);
} finally {
lock.writeLock().unlock();
}
};
for (int i = 0; i < 3; i++) {
new Thread(readTask, "读线程" + i).start();
new Thread(writeTask, "写线程" + i).start();
}
}
}
预期输出(顺序可能不同):
读线程0 读取数据: 0
读线程1 读取数据: 0
读线程2 读取数据: 0
写线程0 写入数据: 1
写线程1 写入数据: 2
写线程2 写入数据: 3
解释:
- 读线程可同时读取,写线程独占修改数据。
3.9 阻塞队列
BlockingQueue
是线程安全的队列,适合生产者-消费者模式。常用实现:ArrayBlockingQueue
。
API用法:
put(E)
:添加元素,队列满时阻塞。take()
:取出元素,队列空时阻塞。
代码示例:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
Thread producer = new Thread(() -> {
try {
queue.put("数据1");
System.out.println("生产数据1");
queue.put("数据2");
System.out.println("生产数据2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者");
Thread consumer = new Thread(() -> {
try {
System.out.println("消费: " + queue.take());
System.out.println("消费: " + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者");
producer.start();
consumer.start();
}
}
预期输出:
生产数据1
消费: 数据1
生产数据2
消费: 数据2
解释:
- 生产者生产数据,消费者消费,队列保证线程安全。
流程图:
3.10 线程池
线程池是Java并发编程中非常重要的一部分,它通过复用线程来减少创建和销毁线程的开销,提高程序性能。JUC提供了 ThreadPoolExecutor
作为原生线程池实现,以及 Executors
工具类来简化线程池的创建。
线程池原理
线程池的核心思想是管理一组线程,让它们重复执行任务,而不是每次任务都创建新线程。线程池的工作流程如下:
- 任务提交:任务通过
submit
或execute
方法提交到线程池。 - 线程分配:如果有空闲线程,任务直接分配给线程执行;如果没有空闲线程,任务可能进入队列等待或触发拒绝策略。
- 任务执行:线程从队列或直接获取任务,执行完成后返回线程池,等待下个任务。
- 资源管理:线程池根据配置(如核心线程数、最大线程数)动态调整线程数量,平衡性能和资源消耗。
线程池的优势:
- 降低开销:避免频繁创建和销毁线程。
- 提高响应速度:复用线程可以立即执行任务。
- 便于管理:统一控制线程数量和任务队列。
ThreadPoolExecutor的七大参数
ThreadPoolExecutor
是线程池的核心实现,构造函数包含以下七个参数,决定了线程池的行为:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
:核心线程数。即使线程空闲,核心线程也不会被销毁,除非设置了allowCoreThreadTimeOut
。maximumPoolSize
:最大线程数。线程池允许创建的最大线程数量,包含核心线程和临时线程。keepAliveTime
:空闲线程存活时间。当非核心线程空闲超过此时间时,会被销毁。unit
:存活时间单位(如秒、毫秒)。workQueue
:任务队列。用于存储等待执行的任务,常见类型有ArrayBlockingQueue
、LinkedBlockingQueue
等。threadFactory
:线程工厂。用于创建新线程,通常使用默认工厂(如Executors.defaultThreadFactory()
)。handler
:拒绝策略。当队列满且线程数达到最大时,新任务会被拒绝。常见策略:AbortPolicy
:抛出异常(默认)。CallerRunsPolicy
:由调用线程执行任务。DiscardPolicy
:丢弃任务,不抛异常。DiscardOldestPolicy
:丢弃队列中最旧的任务。
工作机制:
- 任务提交时:
- 如果线程数 <
corePoolSize
,创建新核心线程。 - 如果线程数 >=
corePoolSize
且队列未满,任务进入队列。 - 如果队列已满且线程数 <
maximumPoolSize
,创建临时线程。 - 如果线程数 >=
maximumPoolSize
且队列满,执行拒绝策略。
- 如果线程数 <
- 空闲的非核心线程在
keepAliveTime
后销毁。
Executors工具类
Executors
提供了便捷方法创建线程池,常见类型包括:
newFixedThreadPool(int n)
:固定大小线程池,核心线程数和最大线程数相等,无临时线程,队列为LinkedBlockingQueue
(无界)。newCachedThreadPool()
:缓存线程池,核心线程数为0,最大线程数为Integer.MAX_VALUE
,线程空闲60秒后销毁,队列为SynchronousQueue
。newSingleThreadExecutor()
:单线程池,只有1个核心线程,队列为LinkedBlockingQueue
。newScheduledThreadPool(int corePoolSize)
:支持定时任务的线程池。
注意:Executors
的某些方法(如 newFixedThreadPool
和 newSingleThreadExecutor
)使用无界队列,可能导致内存溢出,生产环境建议直接使用 ThreadPoolExecutor
自定义配置。
API用法
ThreadPoolExecutor
:execute(Runnable)
:提交无返回值的任务。submit(Callable)
:提交有返回值的任务,返回Future
。shutdown()
:关闭线程池,不接受新任务,等待现有任务完成。shutdownNow()
:立即关闭,尝试中断运行任务,返回未执行任务。
Executors
:newFixedThreadPool(n)
:创建固定线程池。newCachedThreadPool()
:创建动态线程池。submit(Runnable/Callable)
:提交任务。
代码示例1:使用ThreadPoolExecutor
import java.util.concurrent.*;
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(3), // 任务队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
Runnable task = () -> {
System.out.println(Thread.currentThread().getName() + " 执行任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
// 提交8个任务
for (int i = 1; i <= 8; i++) {
try {
pool.execute(task);
} catch (RejectedExecutionException e) {
System.out.println("任务" + i + "被拒绝: " + e.getMessage());
}
}
pool.shutdown();
}
}
预期输出(可能因调度顺序略有不同):
pool-1-thread-1 执行任务
pool-1-thread-2 执行任务
pool-1-thread-3 执行任务
pool-1-thread-4 执行任务
pool-1-thread-5 执行任务
pool-1-thread-1 执行任务
pool-1-thread-2 执行任务
任务8被拒绝: Task rejected
解释:
- 线程池配置:2个核心线程,最大5个线程,队列容量3。
- 提交8个任务:
- 前2个任务分配给2个核心线程。
- 接下来的3个任务进入队列。
- 第6、7个任务触发创建临时线程(总线程数达5)。
- 第8个任务因队列满且线程数达最大,被拒绝(抛出异常)。
- 每个任务执行1秒,线程复用,任务按序完成。
代码示例2:使用Executors
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorsDemo {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
Runnable task = () -> {
System.out.println(Thread.currentThread().getName() + " 执行任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 1; i <= 5; i++) {
pool.submit(task);
}
pool.shutdown();
}
}
预期输出:
pool-1-thread-1 执行任务
pool-1-thread-2 执行任务
pool-1-thread-1 执行任务
pool-1-thread-2 执行任务
pool-1-thread-1 执行任务
解释:
- 使用
newFixedThreadPool(2)
创建2个线程的线程池。 - 提交5个任务,2个线程轮流执行,队列存储多余任务。
- 任务按序完成,无拒绝情况(因队列无界)。
ThreadPoolExecutor流程图
以下是 ThreadPoolExecutor
处理任务的流程:
- 任务提交后,线程池根据核心线程数、队列状态和最大线程数决定处理方式。
- 队列和临时线程提供缓冲,拒绝策略处理超载情况。
- 空闲线程根据
keepAliveTime
决定是否销毁。
注意事项
通过掌握 ThreadPoolExecutor
的七大参数和 Executors
的便捷方法,你可以灵活应对各种并发场景。尝试调整参数,观察线程池行为,会让你对它有更深的理解!
- 自定义ThreadPoolExecutor:生产环境中优先使用
ThreadPoolExecutor
,明确配置参数,避免Executors
默认无界队列导致内存问题。 - 拒绝策略:根据业务选择合适的拒绝策略,如
CallerRunsPolicy
可减缓任务提交速度。 - 关闭线程池:使用
shutdown()
确保任务完成,shutdownNow()
适合紧急停止。
3.11 Fork/Join框架
Fork/Join
适合分解大任务并行执行,如大数据计算。
API用法:
fork()
:异步执行子任务。join()
:等待子任务结果。invoke()
:执行任务并返回结果。
代码示例:
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class ForkJoinDemo {
static class SumTask extends RecursiveTask<Long> {
private final int start;
private final int end;
private static final int THRESHOLD = 100;
SumTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start <= THRESHOLD) {
long sum = 0;
for (int i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
int middle = start + (end - start) / 2;
SumTask left = new SumTask(start, middle);
SumTask right = new SumTask(middle + 1, end);
left.fork();
right.fork();
return left.join() + right.join();
}
}
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
SumTask task = new SumTask(1, 1000);
Long result = pool.invoke(task);
System.out.println("计算结果: " + result);
pool.shutdown();
}
}
预期输出:
计算结果: 500500
解释:
- 计算1到1000的和,任务分解为小块并行执行。
流程图:
3.12 CompletableFuture
CompletableFuture
支持异步编程,结合 Future
和回调机制。
API用法:
supplyAsync(Supplier)
:创建异步任务,返回结果。thenAccept(Consumer)
:处理结果。exceptionally(Function)
:处理异常。thenCombine(CompletionStage, BiFunction)
:合并任务结果。
代码示例:
import java.util.concurrent.CompletableFuture;
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
System.out.println("主线程开始");
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
System.out.println("子线程开始工作");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "子线程完成";
}).exceptionally(ex -> {
System.out.println("异常: " + ex.getMessage());
return "出错啦";
});
future.thenAccept(result -> {
System.out.println("结果: " + result);
});
System.out.println("主线程继续执行");
Thread.sleep(3000);
}
}
预期输出:
主线程开始
主线程继续执行
子线程开始工作
结果: 子线程完成
解释:
- 子线程异步执行,主线程不阻塞。
exceptionally
捕获可能的异常。
4. 小结
JUC提供了强大的并发工具,从锁到线程池,从队列到异步任务,满足各种多线程需求。新手可以按照本文顺序将其各种API都敲一遍,尝试使用,在未来的学习中再去学习其底层的原理。
最后如果大家喜欢我这篇文章,不如给我一个大拇指
👍和小星星
⭐,支持一下白晨吧!喜欢白晨的话,不如关注
👀白晨,以便看到最新更新哟!!!
我是不太能熬夜的白晨,我们下篇文章见。