【JUC】Java并发编程入门:JUC核心API解析

Java并发编程JUC核心API解析

😀大家好,我是白晨,一个不是很能熬夜😫,但是也想日更的人✈。如果喜欢这篇文章,点个赞👍,关注一下👀白晨吧!你的支持就是我最大的动力!💪💪💪

在这里插入图片描述

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毫秒。
  • 创建两个线程 t1t2,分别执行相同的任务。
  • start() 启动线程,join() 确保主线程等待子线程完成。
  • 线程并发运行,输出可能交错,但最终主线程在子线程结束后打印。

流程图

主线程
创建Runnable任务
创建线程1
创建线程2
线程1.start
线程2.start
线程1执行run
线程2执行run
循环打印3次
循环打印3次
线程1结束
线程2结束
主线程.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,证明线程安全。

流程图

成功
失败
线程1/线程2开始
尝试获取lock对象锁
进入synchronized块
执行count++
释放锁
循环1000次
线程退出
等待锁

补充: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

流程图

成功
失败
线程1/线程2开始
尝试获取锁
lock.lock
执行count++
lock.unlock
循环1000次
线程退出
等待锁

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() 唤醒消费者。

流程图

消费者线程
lock.lock
ready == false
condition.await
处理数据
等待signal
lock.unlock
消费者退出
生产者线程
lock.lock
准备数据
ready = true
condition.signal
lock.unlock
生产者退出

3.5 集合的线程安全

JUC提供线程安全的集合,如 ConcurrentHashMapCopyOnWriteArrayList

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接口

CallableRunnable 更灵活,支持返回值和抛出异常。

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

解释

  • 生产者生产数据,消费者消费,队列保证线程安全。

流程图

生产者线程
queue.put
队列满
添加成功
阻塞等待
打印生产信息
消费者线程
queue.take
队列空
取出数据
阻塞等待
打印消费信息

3.10 线程池

线程池是Java并发编程中非常重要的一部分,它通过复用线程来减少创建和销毁线程的开销,提高程序性能。JUC提供了 ThreadPoolExecutor 作为原生线程池实现,以及 Executors 工具类来简化线程池的创建。

线程池原理

线程池的核心思想是管理一组线程,让它们重复执行任务,而不是每次任务都创建新线程。线程池的工作流程如下:

  1. 任务提交:任务通过 submitexecute 方法提交到线程池。
  2. 线程分配:如果有空闲线程,任务直接分配给线程执行;如果没有空闲线程,任务可能进入队列等待或触发拒绝策略。
  3. 任务执行:线程从队列或直接获取任务,执行完成后返回线程池,等待下个任务。
  4. 资源管理:线程池根据配置(如核心线程数、最大线程数)动态调整线程数量,平衡性能和资源消耗。

线程池的优势:

  • 降低开销:避免频繁创建和销毁线程。
  • 提高响应速度:复用线程可以立即执行任务。
  • 便于管理:统一控制线程数量和任务队列。
ThreadPoolExecutor的七大参数

ThreadPoolExecutor 是线程池的核心实现,构造函数包含以下七个参数,决定了线程池的行为:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  1. corePoolSize:核心线程数。即使线程空闲,核心线程也不会被销毁,除非设置了 allowCoreThreadTimeOut
  2. maximumPoolSize:最大线程数。线程池允许创建的最大线程数量,包含核心线程和临时线程。
  3. keepAliveTime:空闲线程存活时间。当非核心线程空闲超过此时间时,会被销毁。
  4. unit:存活时间单位(如秒、毫秒)。
  5. workQueue:任务队列。用于存储等待执行的任务,常见类型有 ArrayBlockingQueueLinkedBlockingQueue 等。
  6. threadFactory:线程工厂。用于创建新线程,通常使用默认工厂(如 Executors.defaultThreadFactory())。
  7. 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 的某些方法(如 newFixedThreadPoolnewSingleThreadExecutor)使用无界队列,可能导致内存溢出,生产环境建议直接使用 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 处理任务的流程:

非核心线程
核心线程
提交任务
线程数 < corePoolSize?
创建核心线程
执行任务
队列未满?
任务入队列
线程数 < maximumPoolSize?
创建临时线程
执行拒绝策略
等待空闲线程
任务完成
线程空闲
空闲时间 > keepAliveTime?
销毁线程
  • 任务提交后,线程池根据核心线程数、队列状态和最大线程数决定处理方式。
  • 队列和临时线程提供缓冲,拒绝策略处理超载情况。
  • 空闲线程根据 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的和,任务分解为小块并行执行。

流程图

主任务1-1000
范围<=100
直接计算
分解任务
左任务1-500
右任务501-1000
left.fork
right.fork
left.join
right.join
合并结果
返回结果

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都敲一遍,尝试使用,在未来的学习中再去学习其底层的原理。

最后如果大家喜欢我这篇文章,不如给我一个大拇指 👍和小星星 ⭐,支持一下白晨吧!喜欢白晨的话,不如关注👀白晨,以便看到最新更新哟!!!

我是不太能熬夜的白晨,我们下篇文章见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白晨并不是很能熬夜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值