前言
本篇文章,主要是列举了线程模块的核心知识和技术。内容分两个模块一个是简单介绍,第二是深入学习。深入学习部分都是推荐的博客文章。
内容有点多,欢迎收藏,可当做java线程模块学习路线,以及面试突击。
java线程核心知识及API
一、线程基础
1.1 线程生命周期
-
新建状态(New):new Thread 此时线程对象已经被创建,但还没有开始运行。
-
就绪状态(Runnable):调用
start()
方法后,线程进入就绪状态,可能还没有被分配到CPU时间片。 -
运行状态(Running):线程获得CPU时间片并开始执行时,线程进入运行状态,执行
run()
方法 -
阻塞状态(Blocked):线程因为某些原因无法继续执行时,线程进入阻塞状态。阻塞状态可以分为多种类型,如等待I/O、等待锁、等待信号等。
-
等待状态(Waiting):线程需要等待某些条件满足时,线程进入等待状态。等待状态可以通过
wait()
方法、join()
方法等实现。 -
计时等待状态(Timed Waiting):当线程需要等待一定时间或者等待某些条件满足时,线程进入计时等待状态。计时等待状态可以通过sleep()方法、wait(timeout)方法等实现。
-
终止状态(Terminated):当线程完成了任务或者因为异常等原因退出时,线程进入终止状态。此时线程的生命周期结束。
深入学习
- 生命周期详细推荐阅读:www.cnblogs.com/huigui-mint…
1.2 创建线程方式(线程池点学习)
- 继承Thread类(不推荐)
class MyThread extends Thread {
@Override
public void run() {
System.out.println("这是通过继承Thread类创建的线程在执行任务");
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
- 实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这是通过实现Runnable接口创建的线程在执行任务");
}
}
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
- 实现Callable接口 + FutureTask
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "这是通过实现Callable接口创建的线程执行结果";
}
}
public class Main {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
try {
String result = futureTask.get();
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
- 线程池创建(推荐方式,一般使用构造函数)
class MyTask implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行任务");
}
}
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executorService.submit(new MyTask());
}
executorService.shutdown();
}
}
线程池深入学习
线程池算是实际开发中,多线程用得最多的一个技术了,也是初中级面试必问问题,下面几篇文章可以重点阅读:
- 线程池入门篇: juejin.cn/post/731449…
- 线程池核心参数、及动态调节:juejin.cn/post/731670…
- 线程池原理分析:tech.meituan.com/2020/04/02/…
1.3线程间协作(基础方法)
wait/sleep、notify/notifyAll、join 这些基础方法需要了解
wait 和 sleep的区别:
-
sleep()方法可以在任何地方使用;而wait()方法则只能在同步方法或同步块中使用。
-
wait 方法会释放对象锁,但 sleep 方法不会。
-
wait的线程会进入到WAITING状态,直到被唤醒;sleep的线程会进入到TIMED_WAITING状态,等到指定时间之后会再尝试获取CPU时间片。
notify/notifyAll:
都是唤醒,等待获取当前对象的线程,notify
是唤醒一个,notifyAll
是唤醒所有有,当然只有一个能成功获取对象锁资源。
join():
join就是把子线程加入到当前主线程中,也就是主线程要阻塞在这里,等子线程执行完之后再继续执行.
常用来控制线程的顺序执行。
二、线程同步机制
2.1 synchronized关键字
// 实例方法锁
public synchronized void method() {}
// 代码块锁
public void method() {
synchronized(this) {
// 临界区
}
}
// 类锁
public static synchronized void staticMethod() {}
深入学习
- synchronized 锁底层核心原理:www.cnblogs.com/java-six/p/…
- synchronized 锁底层原理剖析:blog.csdn.net/weixin_3943…
2.2 Lock体系
ReentrantLock lock = new ReentrantLock(true); // 公平锁
lock.lock();
try {
// 临界区
} finally {
lock.unlock();
}
深入学习
- lock体系以及原理:blog.csdn.net/yuiop123455…
2.3 volatile关键字
volatile
通过内存屏障和缓存一致性协议实现可见性和有序性,是一种轻量级的同步工具。尽管它无法替代锁(如synchronized
),但在特定场景下能显著提升性能。
- 保证可见性
- 禁止指令重排序
- 不保证原子性(需配合synchronized或原子类)
常当开关使用:
public class Test {
private static boolean stop = false;
public static void main(String[] args) {
Thread workerThread = new Thread(() -> {
int count = 0;
// 按照没有用volatile修饰,当前线程不能看到变量被修改,所以理论上来说不会退出循环
while (!stop) {
System.out.println("Worker thread running...");
count++;
}
System.out.println("Worker thread stopped, count: " + count);
});
workerThread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
stop = true;
System.out.println("Main thread set stop to true");
}
}
深入学习
- volatile 底层原理,应用场景:blog.csdn.net/weixin_3943…
- vloatile 可见性测试之坑:juejin.cn/post/748632…
三、并发工具类(JUC)
3.1 java.util.concurrent.atomic
从 JDK1.5 开始提供,用于支持在单个变量上进行无锁的线程安全编程。
基于volatile
和 CAS(compare - and - swap)算法,volatile
保证内存可见性,CAS 保证原子性
常用的基础类:AtomicBoolean
、AtomicInteger
、AtomicLong
,用于原子方式更新对应的基本类型值。
代码示例
public class AtomicIntegerExample {
public static void main(String[] args) {
// 创建一个初始值为 0 的 AtomicInteger 对象
AtomicInteger atomicInt = new AtomicInteger(0);
// 创建并启动 5 个工作线程
for (int i = 0; i < 5; i++) {
new Worker(atomicInt).start();
}
try {
// 主线程休眠一段时间,等待所有工作线程完成
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出最终的计数值
System.out.println("最终计数值: " + atomicInt.get());
}
static class Worker extends Thread {
private final AtomicInteger atomicInt;
public Worker(AtomicInteger atomicInt) {
this.atomicInt = atomicInt;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
// 原子性地将计数值加 1
atomicInt.incrementAndGet();
}
}
}
}
深入学习
- AutoInteger详解:blog.51cto.com/u_11354383/…
- Unsafe 详解: blog.51cto.com/zhangxuelia…
3.2 CountDownLatch
CountDownLatch
用于让一个或多个线程等待其他线程完成操作。它通过一个计数器来实现,初始值为需要等待的线程数量。每个线程完成操作后,计数器减 1,当计数器为 0 时,等待的线程将被唤醒。
应用场景
- 并行任务协调:当有多个并行任务需要执行,且主线程需要等待这些任务全部完成后再继续执行时,可以使用
CountDownLatch
。例如,一个数据处理系统,需要同时从多个数据源获取数据,主线程需要等待所有数据源的数据都获取完成后,再进行后续的数据整合和分析。 - 资源初始化:在系统启动时,可能需要初始化多个资源,如数据库连接、网络连接等。可以为每个资源的初始化任务分配一个线程,使用
CountDownLatch
让主线程等待所有资源初始化完成后再启动系统的其他服务。
代码示例
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
// 创建一个 CountDownLatch 对象,计数器初始值为 3
CountDownLatch latch = new CountDownLatch(3);
// 创建并启动 3 个工作线程
for (int i = 0; i < 3; i++) {
new Worker(latch).start();
}
// 主线程等待所有工作线程完成
System.out.println("主线程等待工作线程完成...");
latch.await();
System.out.println("所有工作线程已完成,主线程继续执行。");
}
static class Worker extends Thread {
private final CountDownLatch latch;
public Worker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " 开始工作...");
// 模拟工作耗时
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " 工作完成。");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 工作完成,计数器减 1
latch.countDown();
}
}
}
}
3.3 CyclicBarrier
CyclicBarrier
用于让一组线程在某个点上进行同步。当所有线程都到达该点时,它们将继续执行。CyclicBarrier
可以重复使用,即当所有线程通过屏障后,屏障可以重置,等待下一组线程。
应用场景
- 多线程任务分阶段执行:例如在一个大型数据处理系统中,需要多个线程同时处理数据的不同部分,每个线程完成自己的部分后,需要等待其他线程也完成,然后一起进行下一步的数据整合操作。
- 并行计算:在进行并行计算时,多个线程同时进行计算任务,计算完成后需要一起汇总结果。使用
CyclicBarrier
可以确保所有线程都完成计算后再进行结果汇总。
代码示例
public class CyclicBarrierExample {
public static void main(String[] args) {
// 创建一个 CyclicBarrier 对象,指定参与等待的线程数量和所有线程到达屏障后要执行的任务
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("所有线程都已到达屏障,开始下一步操作"));
// 创建并启动 3 个工作线程
for (int i = 0; i < 3; i++) {
new Worker(barrier).start();
}
}
static class Worker extends Thread {
private final CyclicBarrier barrier;
public Worker(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " 开始工作...");
// 模拟工作耗时
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " 到达屏障,等待其他线程...");
// 线程到达屏障并等待
barrier.await();
System.out.println(Thread.currentThread().getName() + " 继续执行后续操作。");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
3.4 Semaphore
Semaphore
用于控制同时访问某个资源的线程数量。它通过一个许可证计数器来实现,线程在访问资源前需要获取许可证,访问完成后释放许可证。
应用场景
- 资源池管理:例如数据库连接池、线程池等,
Semaphore
可以用来控制同时使用资源的线程数量,避免资源过度使用。 - 限流:在高并发系统中,为了防止系统被过多的请求压垮,可以使用
Semaphore
对请求进行限流,只允许一定数量的请求同时处理。
代码示例
public static void main(String[] args) {
// 创建一个 Semaphore 对象,初始许可数量为 3
Semaphore semaphore = new Semaphore(3);
// 创建并启动 5 个工作线程
for (int i = 0; i < 5; i++) {
new Worker(semaphore).start();
}
}
static class Worker extends Thread {
private final Semaphore semaphore;
public Worker(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run() {
try {
// 获取许可
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 已获取许可,开始工作...");
// 模拟工作耗时
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " 工作完成,释放许可。");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 释放许可
semaphore.release();
}
}
}
3.5 CompletableFuture
CompletableFuture
是 Java 8 引入的一个强大的异步编程工具类,它实现了 Future
接口和 CompletionStage
接口,结合了 Future
的异步操作能力和 CompletionStage
的流式处理和组合能力,能让开发者以更简洁、灵活的方式处理异步任务。以下从几个方面详细介绍:
应用场景
- 异步 I/O 操作:在处理网络请求、文件读写等 I/O 密集型操作时,使用
CompletableFuture
可以避免阻塞主线程,提高程序的响应性能。 - 并行计算:将一个大任务拆分成多个小任务,使用
CompletableFuture
并行执行这些小任务,最后合并结果,提高计算效率。 - 微服务调用:在微服务架构中,调用多个服务时可以使用
CompletableFuture
实现异步调用,减少服务之间的等待时间。
代码示例
public static void main(String[] args) {
List<Integer> nums = Arrays.asList(1, 11,34,12, 23, 34,45, 16, 27, 38, 19, 10);
List<CompletableFuture<Integer>> futures = nums.stream()
.map(value -> CompletableFuture.supplyAsync(() -> {
// 这里是每个异步任务要执行的操作,
return value*2;
}))
.collect(Collectors.toList());
CompletableFuture<Integer> sumFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApplyAsync(v -> {
// 所有异步计算任务完成后,将它们的结果进行合并
int sum = futures.stream()
.mapToInt(CompletableFuture::join)
.sum();
return sum;
});
int sum = sumFuture.join();
System.out.println(sum);
}
深入学习
- CompletableFuture详解:blog.csdn.net/weixin_4352…
3.6 ForkJoinPool
ForkJoinPool 是基于工作窃取(Work-Stealing)算法实现的线程池,ForkJoinPool 中每个线程都有自己的工作队列,用于存储待执行的任务。当一个线程执行完自己的任务之后,会从其他线程的工作队列中窃取任务执行,以此来实现任务的动态均衡和线程的利用率最大化
应用场景
- 大任务分解为小任务:适用于可以递归分解为更小任务的大型任务。ForkJoinPool 通过分而治之的方式,将大任务拆分为小任务,这些小任务可以并行处理。
- 计算密集型任务:对于需要大量计算且能够并行化的任务,ForkJoinPool 是一个理想的选择。它能够有效利用多核处理器的优势来加速处理过程。
- 异构任务并行处理:当任务之间没有或很少有依赖性时,ForkJoinPool 可以帮助并行执行这些任务,从而提高效率。
- 递归算法的并行化:适合于可以用递归方法解决的问题,如快速排序、归并排序、图像处理中的分区算法等。
- 数据聚合任务:在处理需要聚合多个数据源结果的任务时(例如,遍历树结构并聚合结果),ForkJoinPool 提供了有效的方式来并行化这一过程。
代码示例
ForkJoinPool 实现快排的代码
public class TestForkJoinPool extends RecursiveAction {
private int[] array;
private int left;
private int right;
public TestForkJoinPool(int[] array, int left, int right) {
this.array = array;
this.left = left;
this.right = right;
}
private int partition(int left, int right) {
int pivot = array[right];
int i = left - 1;
for (int j = left; j < right; j++) {
if (array[j] <= pivot) {
i++;
// Swap array[i] and array[j]
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
// Swap array[i+1] and array[right] (or pivot)
int temp = array[i + 1];
array[i + 1] = array[right];
array[right] = temp;
return i + 1;
}
@Override
protected void compute() {
if (left < right) {
int partitionIndex = partition(left, right);
// Parallelize the two subtasks
TestForkJoinPool leftTask = new TestForkJoinPool(array, left, partitionIndex - 1);
TestForkJoinPool rightTask = new TestForkJoinPool(array, partitionIndex + 1, right);
leftTask.fork();
rightTask.fork();
leftTask.join();
rightTask.join();
}
}
public static void TestForkJoinPool(int[] array) {
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new TestForkJoinPool(array, 0, array.length - 1));
}
public static void main(String[] args) {
int[] array = { 12, 35, 87, 26, 9, 28, 7 };
TestForkJoinPool(array);
for (int i : array) {
System.out.print(i + " ");
}
}
}
深入学习
- ForkJoinPool原理分析:blog.csdn.net/qq_45061342…
总结
掌握Java多线程开发需要理解线程基础、同步机制、线程协作等核心概念,同时要熟悉JUC工具包的使用。建议通过实际项目中的并发场景(如秒杀系统、批量处理等)加深理解。良好的并发程序设计需要平衡性能与线程安全,避免过度同步导致的性能问题。
如果佬们觉得还有重要的API,欢迎留言补充