JUC的常见类
JUC的常见类
1. Callable与Runnable接口
Callable接口与Runnable接口的主要区别在于:Callable的call()方法可以返回执行结果并抛出受检异常,而Runnable的run()方法既不能返回结果也不能抛出受检异常。这使得Callable更适合需要获取计算结果或处理复杂异常的场景。
1.1 Runnable的使用
方法一:实现Runnable接口
// 实现 Runnable 接口
class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + ": " + i);try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class RunnableExample {public static void main(String[] args) {// 创建 Runnable 实例MyRunnable myRunnable = new MyRunnable();// 创建线程并启动Thread thread1 = new Thread(myRunnable, "线程1");Thread thread2 = new Thread(myRunnable, "线程2");thread1.start();thread2.start();}
}
方法二:使用匿名内部类
public class AnonymousRunnable {public static void main(String[] args) {// 使用匿名内部类Thread thread = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 3; i++) {System.out.println("匿名内部类线程: " + i);}}});thread.start();}
}
方法三:使用 Lambda 表达式
public class LambdaRunnable {public static void main(String[] args) {// 使用 Lambda 表达式Thread thread = new Thread(() -> {for (int i = 0; i < 3; i++) {System.out.println("Lambda线程: " + i);}});thread.start();// 更简洁的写法new Thread(() -> System.out.println("简洁的Lambda线程")).start();}
}
方法四:使用 ExecutorService 执行 Runnable
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ExecutorServiceExample {public static void main(String[] args) {// 创建线程池ExecutorService executor = Executors.newFixedThreadPool(3);// 提交多个任务for (int i = 0; i < 5; i++) {final int taskId = i;executor.execute(() -> {System.out.println("执行任务 " + taskId + " 在线程: " + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}});}// 关闭线程池executor.shutdown();}
}
1.2 Callable的使用
方法一:使用Callable接口
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;// 实现 Callable 接口
class MyCallable implements Callable<String> {private final String name;public MyCallable(String name) {this.name = name;}@Overridepublic String call() throws Exception {System.out.println(Thread.currentThread().getName() + " 开始执行任务: " + name);Thread.sleep(2000); // 模拟耗时操作System.out.println(Thread.currentThread().getName() + " 完成任务: " + name);return "任务 " + name + " 的执行结果";}
}public class CallableExample {public static void main(String[] args) throws Exception {// 创建 Callable 实例Callable<String> callable = new MyCallable("测试任务");// 使用 FutureTask 包装 CallableFutureTask<String> futureTask = new FutureTask<>(callable);// 创建线程并启动Thread thread = new Thread(futureTask);thread.start();// 获取结果(会阻塞直到任务完成)String result = futureTask.get();System.out.println("任务结果: " + result);}
}
方法二:使用Lambda表达式
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;public class LambdaCallable {public static void main(String[] args)throws Exception {// 使用 Lambda 表达式创建 CallableCallable<Integer> callable = () -> {int sum = 0;for (int i = 1; i <= 100; i++) {sum += i;Thread.sleep(10); // 模拟计算过程}return sum;};FutureTask<Integer> futureTask = new FutureTask<>(callable);new Thread(futureTask).start();// 可以在这里执行其他任务...System.out.println("等待计算结果中...");Integer result = futureTask.get();System.out.println("1-100的和为: " + result); // 输出: 5050}
}
方法三:使用 ExecutorService 执行 Callable
import java.util.concurrent.*;
import java.util.*;public class ExecutorServiceCallable {public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(3);// 创建多个 Callable 任务List<Callable<String>> tasks = new ArrayList<>();for (int i = 1; i <= 5; i++) {final int taskId = i;tasks.add(() -> {System.out.println("执行任务 " + taskId + " 在线程: " + Thread.currentThread().getName());Thread.sleep(1000);return "任务 " + taskId + " 完成";});}try {// 提交所有任务并获取 Future 列表List<Future<String>> futures = executor.invokeAll(tasks);// 获取所有任务结果for (Future<String> future : futures) {try {String result = future.get();System.out.println("结果: " + result);} catch (ExecutionException e) {System.out.println("任务执行异常: " + e.getMessage());}}} catch (InterruptedException e) {e.printStackTrace();} finally {executor.shutdown();}}
}
1.3 理解Callable
代码示例: 创建线程计算 1 + 2 + 3 + … + 1000, 不使⽤ Callable 版本
//创建⼀个类 Result , 包含⼀个 sum 表⽰最终结果, lock 表⽰线程同步使⽤的锁对象static class Result {public int sum;public Object lock;}//main ⽅法中先创建 Result 实例, 然后创建⼀个线程 t. 在线程内部计算 1 + 2 + 3 + ... + 1000.public static void main(String[] args) throws InterruptedException {Result result = new Result();Thread t = new Thread() {//当线程 t 计算完毕后, 通过 notify 唤醒主线程, 主线程再打印结果@Overridepublic void run() {int sum = 0;for (int i = 1; i <= 1000; i++) {sum += i;}synchronized (result.lock) {result.sum = sum;result.lock.notify();}}};t.start();//主线程同时使⽤ wait 等待线程 t 计算结束. (注意, 如果执⾏到 wait 之前, 线程 t 已经计算完了, 就不//必等待了).synchronized (result.lock) {while (result.sum == 0) {result.lock.wait();}System.out.println(result.sum);}}
代码示例: 创建线程计算 1 + 2 + 3 + … + 1000, 使⽤ Callable 版本
public static void main(String[] args) throws Exception {//创建⼀个匿名内部类, 实现 Callable 接⼝. Callable 带有泛型参数. 泛型参数表⽰返回值的类型Callable<Integer> callable = new Callable<Integer>() {//重写 Callable 的 call ⽅法, 完成累加的过程. 直接通过返回值返回计算结果@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 1; i <= 1000; i++) {sum += i;}return sum;}};//把 callable 实例使⽤ FutureTask 包装⼀下FutureTask<Integer> futureTask = new FutureTask<>(callable);//创建线程, 线程的构造⽅法传⼊ FutureTask . 此时新线程就会执⾏ FutureTask 内部的 Callable 的//call ⽅法, 完成计算. 计算结果就放到了 FutureTask 对象中Thread t = new Thread(futureTask);t.start();//在主线程中调⽤ futureTask.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的//结果int result = futureTask.get();System.out.println(result);}
- Callable 和 Runnable 相对, 都是描述⼀个 “任务”. Callable 描述的是带有返回值的任务, Runnable
- 描述的是不带返回值的任务.
- Callable 通常需要搭配 FutureTask 来使⽤. FutureTask ⽤来保存Callable 的返回结果. 因为Callable 往往是在另⼀个线程中执⾏的, 啥时候执⾏完并不确定.
- FutureTask 就可以负责这个等待结果出来的⼯作
- FutureTask 是 Java 并发编程中的一个重要类
- 它实现了 Future 接口和 Runnable 接口
- 可以作为 Runnable 被线程执行
- 可以作为 Future 获取异步计算的结果
举个栗子: 去食堂点餐,前台会给你一张小票,你可以凭借这张小票取餐,而FutureTask就类似于小票的作用。
2. ReentrantLock
ReentrantLock 是 Java 并发包中一个重要的可重入互斥锁,它提供了比 synchronized 更灵活的锁机制。
2.1 ReentrantLock 的⽤法:
- lock(): 加锁, 如果获取不到锁就死等
- trylock(超时时间): 加锁, 如果获取不到锁, 等待⼀定的时间之后就放弃加锁
- unlock(): 解锁
ReentrantLock lock = new ReentrantLock();
-----------------------------------------
lock.lock();
try { // working
} finally { lock.unlock()
}
2.2 ReentrantLock 和 synchronized 的区别
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 实现级别 | JVM 原生关键字 | JDK API 类 |
| 锁的获取 | 自动获取和释放 | 手动获取和释放 |
| 可重入性 | 支持 | 支持 |
| 公平性 | 非公平 | 可选择公平/非公平 |
| 可中断性 | 不支持 | 支持 |
| 超时机制 | 不支持 | 支持 |
| 条件变量 | 单个隐式条件 | 多个显式条件 |
| 性能 | 优化后很好 | 高竞争下更好 |
| 代码灵活性 | 较低 | 较高 |
选择建议:
- 优先使用 synchronized:代码简单,自动管理,不易出错
- 需要高级功能时使用 ReentrantLock:可中断、超时、公平性、多个条件
- 性能关键且高竞争:考虑 ReentrantLock
- 代码可维护性:synchronized 更简洁
两者都是线程安全的有效工具,选择取决于具体需求和场景复杂度。
3. 信号量 Semaphore
3.1 什么是信号量?
信号量是一种在并发编程中使用的同步工具,用于控制对共享资源的访问或执行特定操作的线程数量。它维护了一个许可集,线程可以通过获取和释放许可来协调它们的行为。
你可以把它想象成一个管理员,管理着一个有限的停车场。
- 信号量 = 停车场管理员
- 许可数 = 停车位的总数
- 线程 = 想要停车的汽车
- 获取许可 = 汽车进入停车场(如果车位已满,则必须等待)
- 释放许可 = 汽车离开停车场(空出一个车位,让等待的汽车进入)
3.2 Java 中的 Semaphore 类
public static void main(String[] args) {// 4 个线程同时访问, 但是只有 4 个线程能够获取到资源Semaphore semaphore = new Semaphore(4);Runnable runnable = new Runnable() {// 每个线程执行的任务@Overridepublic void run() {try {System.out.println("申请资源");// 申请资源semaphore.acquire();System.out.println("我获取到资源了");Thread.sleep(1000);System.out.println("我释放资源了");// 释放资源semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}}};for (int i = 0; i < 20; i++) {Thread t = new Thread(runnable);t.start();}}
4. CountDownLatch
4.1 什么是CountDownLatch?
CountDownLatch 是 Java 并发包中一个非常实用的同步工具类,它允许一个或多个线程等待其他一组线程完成操作。
你可以把它想象成一个倒计时计数器或者跑步比赛的起跑点:
- 计数器 = 比赛开始前需要做的准备工作数量
- 裁判 (主线程) = 等待所有准备工作就绪
- 工作人员 (其他线程) = 执行各项准备工作
- countDown() = 一项准备工作完成
- await() = 裁判等待所有准备工作完成
当计数器减到零时,所有等待的线程(裁判)就会被释放,可以继续执行。
4.2 核心机制:
CountDownLatch 使用一个计数器进行初始化,线程通过调用以下两个方法来协调:
- await():使当前线程等待,直到计数器减到零(除非线程被中断)。
- countDown():将计数器减 1。如果计数器达到零,则释放所有等待的线程。
重要特性:
- 计数器无法重置。一旦计数器到达零,它就永远处于触发状态。
- 这使它特别适合于"一次性"的场景。
public static void main(String[] args) throws Exception {// 10 个线程等待其他线程执行完毕CountDownLatch latch = new CountDownLatch(10);Runnable r = new Runnable() {// 每个线程执行的任务@Overridepublic void run() {try {Thread.sleep((long) (Math.random() * 10000));// 每个线程执行完毕后, 计数器减 1latch.countDown();} catch (Exception e) {e.printStackTrace();}}};// 10 个线程同时执行任务for (int i = 0; i < 10; i++) {new Thread(r).start();System.out.println("线程" + i + "等待其他线程");}// 等待所有线程执行完毕, 计数器减到 0 时, 继续执行后续代码latch.await();System.out.println("所有线程执行完毕");}
