Java开发工程师必须掌握的线程知识指南

前言

本篇文章,主要是列举了线程模块的核心知识和技术。内容分两个模块一个是简单介绍,第二是深入学习。深入学习部分都是推荐的博客文章

内容有点多,欢迎收藏,可当做java线程模块学习路线,以及面试突击。

java线程核心知识及API

一、线程基础

1.1 线程生命周期

  1. 新建状态(New):new Thread 此时线程对象已经被创建,但还没有开始运行。

  2. 就绪状态(Runnable):调用start()方法后,线程进入就绪状态,可能还没有被分配到CPU时间片。

  3. 运行状态(Running):线程获得CPU时间片并开始执行时,线程进入运行状态,执行run()方法

  4. 阻塞状态(Blocked):线程因为某些原因无法继续执行时,线程进入阻塞状态。阻塞状态可以分为多种类型,如等待I/O、等待锁、等待信号等。

  5. 等待状态(Waiting):线程需要等待某些条件满足时,线程进入等待状态。等待状态可以通过wait()方法、join()方法等实现。

  6. 计时等待状态(Timed Waiting):当线程需要等待一定时间或者等待某些条件满足时,线程进入计时等待状态。计时等待状态可以通过sleep()方法、wait(timeout)方法等实现。

  7. 终止状态(Terminated):当线程完成了任务或者因为异常等原因退出时,线程进入终止状态。此时线程的生命周期结束。

深入学习
  1. 生命周期详细推荐阅读:www.cnblogs.com/huigui-mint...

1.2 创建线程方式(线程池点学习)

  • 继承Thread类(不推荐)
java 复制代码
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接口
java 复制代码
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
java 复制代码
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();
        }
    }
}
  • 线程池创建(推荐方式,一般使用构造函数)
java 复制代码
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();
    }
}
线程池深入学习

线程池算是实际开发中,多线程用得最多的一个技术了,也是初中级面试必问问题,下面几篇文章可以重点阅读:

  1. 线程池入门篇: juejin.cn/post/731449...
  2. 线程池核心参数、及动态调节:juejin.cn/post/731670...
  3. 线程池原理分析:tech.meituan.com/2020/04/02/...

1.3线程间协作(基础方法)

wait/sleep、notify/notifyAll、join 这些基础方法需要了解

wait 和 sleep的区别:

  1. sleep()方法可以在任何地方使用;而wait()方法则只能在同步方法或同步块中使用。

  2. wait 方法会释放对象锁,但 sleep 方法不会。

  3. wait的线程会进入到WAITING状态,直到被唤醒;sleep的线程会进入到TIMED_WAITING状态,等到指定时间之后会再尝试获取CPU时间片。

notify/notifyAll: 都是唤醒,等待获取当前对象的线程,notify是唤醒一个,notifyAll是唤醒所有有,当然只有一个能成功获取对象锁资源。

join():

join就是把子线程加入到当前主线程中,也就是主线程要阻塞在这里,等子线程执行完之后再继续执行.

常用来控制线程的顺序执行。

推荐阅读: blog.csdn.net/weixin_3943...

二、线程同步机制

2.1 synchronized关键字

java 复制代码
// 实例方法锁
public synchronized void method() {}

// 代码块锁
public void method() {
    synchronized(this) {
        // 临界区
    }
}

// 类锁
public static synchronized void staticMethod() {}
深入学习
  1. synchronized 锁底层核心原理:www.cnblogs.com/java-six/p/...
  2. synchronized 锁底层原理剖析:blog.csdn.net/weixin_3943...

2.2 Lock体系

java 复制代码
ReentrantLock lock = new ReentrantLock(true); // 公平锁
lock.lock();
try {
    // 临界区
} finally {
    lock.unlock();
}
深入学习
  1. lock体系以及原理:blog.csdn.net/yuiop123455...

2.3 volatile关键字

volatile通过内存屏障缓存一致性协议 实现可见性和有序性,是一种轻量级的同步工具。尽管它无法替代锁(如synchronized),但在特定场景下能显著提升性能。

  • 保证可见性
  • 禁止指令重排序
  • 不保证原子性(需配合synchronized或原子类)

常当开关使用:

java 复制代码
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");
    }

}
深入学习
  1. volatile 底层原理,应用场景:blog.csdn.net/weixin_3943...
  2. vloatile 可见性测试之坑:juejin.cn/post/748632...

三、并发工具类(JUC)

3.1 java.util.concurrent.atomic

从 JDK1.5 开始提供,用于支持在单个变量上进行无锁的线程安全编程。 基于volatile和 CAS(compare - and - swap)算法,volatile保证内存可见性,CAS 保证原子性

常用的基础类:AtomicBooleanAtomicIntegerAtomicLong,用于原子方式更新对应的基本类型值。

代码示例
java 复制代码
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();
            }
        }
    }
}
深入学习
  1. AutoInteger详解:blog.51cto.com/u_11354383/...
  2. Unsafe 详解: blog.51cto.com/zhangxuelia...

3.2 CountDownLatch

CountDownLatch 用于让一个或多个线程等待其他线程完成操作。它通过一个计数器来实现,初始值为需要等待的线程数量。每个线程完成操作后,计数器减 1,当计数器为 0 时,等待的线程将被唤醒。

应用场景
  • 并行任务协调 :当有多个并行任务需要执行,且主线程需要等待这些任务全部完成后再继续执行时,可以使用 CountDownLatch。例如,一个数据处理系统,需要同时从多个数据源获取数据,主线程需要等待所有数据源的数据都获取完成后,再进行后续的数据整合和分析。
  • 资源初始化 :在系统启动时,可能需要初始化多个资源,如数据库连接、网络连接等。可以为每个资源的初始化任务分配一个线程,使用 CountDownLatch 让主线程等待所有资源初始化完成后再启动系统的其他服务。
代码示例
java 复制代码
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 可以确保所有线程都完成计算后再进行结果汇总。
代码示例
java 复制代码
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 对请求进行限流,只允许一定数量的请求同时处理。
代码示例
java 复制代码
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 的流式处理和组合能力,能让开发者以更简洁、灵活的方式处理异步任务。以下从几个方面详细介绍:

应用场景
  1. 异步 I/O 操作 :在处理网络请求、文件读写等 I/O 密集型操作时,使用 CompletableFuture 可以避免阻塞主线程,提高程序的响应性能。
  2. 并行计算 :将一个大任务拆分成多个小任务,使用 CompletableFuture 并行执行这些小任务,最后合并结果,提高计算效率。
  3. 微服务调用 :在微服务架构中,调用多个服务时可以使用 CompletableFuture 实现异步调用,减少服务之间的等待时间。
代码示例
java 复制代码
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);
}
深入学习
  1. CompletableFuture详解:blog.csdn.net/weixin_4352...

3.6 ForkJoinPool

ForkJoinPool 是基于工作窃取(Work-Stealing)算法实现的线程池,ForkJoinPool 中每个线程都有自己的工作队列,用于存储待执行的任务。当一个线程执行完自己的任务之后,会从其他线程的工作队列中窃取任务执行,以此来实现任务的动态均衡和线程的利用率最大化

应用场景
  1. 大任务分解为小任务:适用于可以递归分解为更小任务的大型任务。ForkJoinPool 通过分而治之的方式,将大任务拆分为小任务,这些小任务可以并行处理。
  2. 计算密集型任务:对于需要大量计算且能够并行化的任务,ForkJoinPool 是一个理想的选择。它能够有效利用多核处理器的优势来加速处理过程。
  3. 异构任务并行处理:当任务之间没有或很少有依赖性时,ForkJoinPool 可以帮助并行执行这些任务,从而提高效率。
  4. 递归算法的并行化:适合于可以用递归方法解决的问题,如快速排序、归并排序、图像处理中的分区算法等。
  5. 数据聚合任务:在处理需要聚合多个数据源结果的任务时(例如,遍历树结构并聚合结果),ForkJoinPool 提供了有效的方式来并行化这一过程。
代码示例

ForkJoinPool 实现快排的代码

java 复制代码
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 + " ");
        }
    }
}
深入学习
  1. ForkJoinPool原理分析:blog.csdn.net/qq_45061342...

总结

掌握Java多线程开发需要理解线程基础、同步机制、线程协作等核心概念,同时要熟悉JUC工具包的使用。建议通过实际项目中的并发场景(如秒杀系统、批量处理等)加深理解。良好的并发程序设计需要平衡性能与线程安全,避免过度同步导致的性能问题。

如果佬们觉得还有重要的API,欢迎留言补充

相关推荐
bing_15811 分钟前
JVM 类加载器在什么情况下会加载一个类?
java·jvm
冬天vs不冷1 小时前
EasyExcel导出自动回显中文,读取自动转换码值(基于全局转换器与自定义注解)
java·excel
阿里云云原生1 小时前
RAG 调优指南:Spring AI Alibaba 模块化 RAG 原理与使用
java·人工智能·spring
天上掉下来个程小白2 小时前
Redis-12.在Java中操作Redis-Spring Data Redis使用方式-操作字符串类型的数据
java·redis·spring·springboot·苍穹外卖
ん贤3 小时前
2023第十四届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组(真题&题解)(C++/Java题解)
java·c语言·数据结构·c++·算法·蓝桥杯
在京奋斗者5 小时前
spring boot自动装配原理
java·spring boot·spring
明天不下雨(牛客同名)7 小时前
为什么 ThreadLocalMap 的 key 是弱引用 value是强引用
java·jvm·算法
多多*8 小时前
Java设计模式 简单工厂模式 工厂方法模式 抽象工厂模式 模版工厂模式 模式对比
java·linux·运维·服务器·stm32·单片机·嵌入式硬件
胡图蛋.9 小时前
Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个?
java·spring boot·后端
牛马baby9 小时前
Java高频面试之并发编程-01
java·开发语言·面试