Java 并发与并行——Java 的并发基石:线程、进程与进阶

55 阅读22分钟

欢迎来到第 2 章!本章我们以“繁忙厨房”为灵感,带你探究 Java 的并发模型。在线条分明、节奏飞快的厨房里,线程就像身手敏捷的副厨,各自以速度与精准处理分配到的工序;它们协同工作,在共享的厨房空间与资源中井然有序地“共舞”,为整道菜的完成各尽其职。
与之相对,进程好比相互独立的大型厨房,各自拥有专属菜单与资源,在自己的领域内自主运行,互不干扰,游刃有余地应对复杂任务。

本章将细致剖析 Java 并发的这两大关键组成:我们会讲解线程的生命周期——它如何被唤醒、履职与休息;也会考察进程的独立性与资源管理。随后将走进 java.util.concurrent 这间“储备充足的食品库”,学习如何高效而协调地“指挥”线程与进程。读完本章,你将掌握管理这些并发要素的扎实方法,为构建健壮高效的 Java 应用打下基础。

技术要求

你需要在电脑上安装一个 Java 集成开发环境(IDE)。以下是常见 IDE 与下载地址:

VS Code 轻量且可高度定制,适合希望低资源占用并按需安装扩展的开发者;但与更成熟的 Java IDE 相比,开箱功能可能不够完整,需要通过扩展补齐。

本章配套代码可在 GitHub 获取:
github.com/PacktPublis…

Java 的“并发厨房”——揭开线程与进程的面纱

掌握 Java 的并发利器——线程与进程——就像练就名厨身手。本节将为你打好设计高效、响应敏捷应用的基本功,让程序在同时处理多任务时也能像米其林厨房一样运转顺畅。

什么是线程与进程?

在 Java 并发领域,线程就像厨房里的副厨。每位副厨(线程)承担特定工序,勤勉完成分内之事;正如副厨共享同一间厨房及其工具,线程也在同一 Java 进程中并行运行,共享内存与资源。
再想象一家大型餐厅:披萨炉房、糕点房、热菜间各自独立——这些就对应进程。与共享同一厨房的线程不同,进程拥有专属资源独立运行,好比不同餐厅互不干扰地制作各自的复杂菜品。

归结起来:线程像共享厨房里的灵巧副厨,进程则像拥有专属厨师与资源的独立厨房。

相同点与不同点

在这间热闹厨房里,线程与进程都为顺畅出餐出力,但方式各异——就像不同专长的厨师。

相同点:

  • 多任务能手:线程与进程都能让 Java 应用并发处理多项任务,像同时服务多张餐桌而不让任何一道菜久等。
  • 资源共享:同一进程内的线程可共享资源;不同进程之间也可在适当配置下共享文件、数据库等,实现高效协作。
  • 独立执行路径:二者都有各自的执行路径,彼此不直接打断,就像不同厨师各按配方行事。

不同点:

  • 作用域:线程存在于单一进程内,共享该进程的内存与资源;进程彼此完全独立,各有“独立厨房”。
  • 隔离性:线程共享内存,易受干扰引发数据破坏;进程隔离更强,安全性更高,可避免“串味”。
  • 创建与管理成本:线程更轻量、创建管理更简单;进程作为独立实体,资源开销更大、控制更复杂。
  • 性能取向:线程切换细粒度高,适合小任务的快速调度;进程各自占有资源,适合较大且独立的工作负载。

线程与进程都是 Java“厨具箱”中的重要工具。理解异同,有助于你因菜选器,烹制“佳肴”(亦即杰出的 Java 应用)。

Java 中线程的生命周期

把线程的生命周期类比副厨的一次值班,我们关注 Java 应用中线程的关键阶段:

  • 新建(New) :通过 new 或继承 Thread 创建线程后进入新建态——副厨已到岗,但尚未开工。
  • 可运行(Runnable) :调用 start() 后进入可运行态——副厨已就绪,等待轮到自己上灶。调度器依据优先级与系统策略分配 CPU 时间。
  • 运行(Running) :一旦获得 CPU 时间,线程开始执行 run()——副厨正式开工处理分内任务。(单个处理器核心上同一时刻仅有一个线程真实运行。)
  • 阻塞/等待(Blocked/Waiting) :当条件不满足而无法推进时进入此态,如等待资源释放;典型场景包含 wait()join()sleep() 等。
  • 计时等待(Timed Waiting) :通过带超时的方法进入计时等待,如 sleep(long)wait(long)——好比副厨按时小憩,时间一到继续上岗。
  • 终止(Terminated)run() 执行完毕或线程被 interrupt() 后进入终止态——副厨完成当班任务、下班离场。终止后的线程不可重启

理解这些状态与 start()/run()/wait()/join()/sleep()/interrupt() 等关键方法的关系,是高效并发编程的基础。
现在,让我们把这些知识带到真实世界,看看线程在日常 Java 应用中是如何大显身手的吧!

活动:在实践场景中区分线程与进程

在充满活力的 Java 并发“厨房”中,下面这段 Java 代码演示了线程(厨师)如何在一个进程(厨房)内执行任务(准备菜肴)。这个类比将帮助你在真实场景中理解线程与进程的概念:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class KitchenSimulator {
    private static final ExecutorService kitchen = Executors.newFixedThreadPool(3);

    public static void main(String[] args) {
        String dishToPrepare = "Spaghetti Bolognese";
        String menuToUpdate = "Today's Specials";

        kitchen.submit(() -> {
            prepareDish(dishToPrepare);
        });

        kitchen.submit(() -> {
            searchRecipes("Italian");
        });

        kitchen.submit(() -> {
            updateMenu(menuToUpdate, "Risotto alla Milanese");
        });

        kitchen.shutdown();
    }

    private static void prepareDish(String dish) {
        System.out.println("Preparing " + dish);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private static void searchRecipes(String cuisine) {
        System.out.println("Searching for " + cuisine + " recipes");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private static void updateMenu(String menu, String dishToAdd) {
        System.out.println("Updating " + menu + " with " + dishToAdd);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

代码中线程角色解析:

  • 厨师(线程) :代码中的每位“厨师”代表一个线程。ExecutorServicekitchen 创建了一个包含 3 个线程的线程池,模拟三位厨师并发工作。
  • 任务:通过 submit() 将任务(做菜、找菜谱、更新菜单)分配给线程池中的各个线程。
  • 并发执行:线程使这些任务可以同时运行,从而提升性能与响应性。
  • 工作模拟:每个任务打印信息后调用 Thread.sleep(1000) 休眠 1 秒,模拟厨师执行该任务的耗时。在休眠期间,其他线程可继续执行,体现程序的并发特性。
  • 异常处理:由于 Thread.sleep() 可能抛出 InterruptedException,每个任务都用 try-catch 包裹;一旦在休眠中被中断,捕获异常并通过 Thread.currentThread().interrupt() 恢复中断标志,确保正确处理中断。

代码中进程角色说明:

  • Java 运行时:整个 Java 程序(包括厨房模拟)运行在单个操作系统进程内。
  • 资源分配:该进程拥有由操作系统分配的独立内存空间,用于管理变量、对象和代码执行。
  • 运行环境:进程为线程的存在与运行提供环境。

关键要点回顾:

  • 进程内的线程:线程是轻量级的执行单元,共享所属进程的内存与资源
  • 并发性:线程让单个进程内可以并发执行多项任务;若有多核 CPU,还可进一步利用并行执行能力。
  • 进程管理:操作系统负责管理进程,分配资源并调度执行

现在,让我们切换视角,探索解锁其全部潜能的工具箱:java.util.concurrent。这个“宝库”中汇集了构建健壮高效并发程序所需的类与接口,为你的 Java 应用应对任何多任务挑战奠定基础!

并发工具箱——java.util.concurrent

把你的 Java 应用想象成一家忙碌的餐厅:订单源源不断,食材要准备,菜品要按时烹制并顺畅出菜。若没有高效的系统来管理,这简直是“灾难配方”!幸运的是,java.util.concurrent 就像餐厅的高科技设备,帮助你理顺流程、避免混乱。借助线程池(管理“厨师”/线程)、锁与队列(协调任务)以及一系列强大的并发工具,你可以像米其林主厨一样指挥你的 Java 应用。深入这套工具箱,解锁打造顺滑、灵敏且高效程序的秘诀,让用户惊艳于你的作品吧。

下面先对这个包中的关键元素一览。

线程与执行器(Threads and executors)

ExecutorServiceThreadPoolExecutor 在并发任务编排中扮演关键角色:

ExecutorService:管理线程池的多面手

  • 抽象接口:提供管理线程池、异步执行任务的高层 API。

  • 聚焦任务管理:封装线程池创建与管理,提供提交任务、控制执行、处理结果的方法。

  • 灵活多样的实现:根据行为选择不同实现,例如:

    • FixedThreadPool:固定线程数
    • CachedThreadPool:按需增长的线程池
    • SingleThreadExecutor:顺序执行
    • ScheduledThreadPool:延迟或周期性任务

ThreadPoolExecutor:ExecutorService 的具体实现

  • 具体实现:核心实现,提供对线程池行为的细粒度控制。

  • 可调参数:可自定义以下关键参数:

    • 核心线程数(core pool size)
    • 最大线程数(maximum pool size)
    • 存活时间(keep-alive time,空闲线程超时)
    • 队列容量(等待任务数)
  • 直接使用:在代码中直接实例化,完全掌控线程池特性(核心/最大线程数、存活时间、队列容量、线程工厂等)。当你需要精细控制时,这种方式更合适。示例如下:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class DirectThreadPoolExample {
    public static void main(String[] args) {
        int corePoolSize = 2;
        int maxPoolSize = 4;
        long keepAliveTime = 5000;
        TimeUnit unit = TimeUnit.MILLISECONDS;
        int taskCount = 15; // 依次改为 4、10、12、14,最后 15,观察输出
        ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);

        ThreadPoolExecutor executor =
            new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue);

        IntStream.range(0, taskCount).forEach(
            i -> executor.execute(
                () -> System.out.println(
                    String.format("Task %d executed. Pool size = %d. Queue size = %d.",
                                  i, executor.getPoolSize(), executor.getQueue().size())
                )
            )
        );

        executor.shutdown();
        executor.close();
    }
}

说明:以上直接用特定参数实例化 ThreadPoolExecutor,得到核心线程 2、最大线程 4、存活时间 5000ms、工作队列容量 10 的线程池。通过 IntStream.range().forEach() 提交任务,每个任务打印任务编号当前池大小队列大小

如何选型?

  • 通用任务管理:多数场景使用 ExecutorService,简单且可按需选择实现。
  • 特定需求:当你需要对线程池配置/行为进行精确控制时,使用 ThreadPoolExecutor

理解二者的优势与适用场景,便可更好地管理线程池,释放并发潜能。

同步与协调(Synchronization and coordination)

在多线程环境中,同步与协调对管理共享资源、确保线程安全至关重要。Java 提供了多种类与接口,应对并发编程中的具体用例:

Lock:对共享资源的精细化控制

  • 独占访问:细粒度地控制关键区,保证同一时刻仅一个线程进入。
  • 用例:保护共享数据结构、协调文件/网络连接访问、避免竞态。

Semaphore:管理有限资源池

  • 资源管理:限制并发访问数量,让多个线程共享有限资源。
  • 用例:限制并发连接数、管理线程池、实现生产者-消费者等。

CountDownLatch:等待一组操作完成

  • 任务协调:要求一组任务完成后再继续;线程在“闸门”处等待,计数归零即放行。
  • 用例:等待多服务启动再对外提供能力;确保初始化完成后再进入主流程;控制测试执行顺序。

CyclicBarrier:可复用的栅栏同步

  • 集合点:让一组线程在公共屏障处汇合,仅当全部到齐后才一起继续。
  • 用例:分块并行计算后再归并;实现“会合点”;不同于 CountDownLatch可重复使用

以上工具各司其职,协同线程、确保和谐执行。

并发集合与原子变量(Concurrent collections and atomic variables)

并发集合为多线程环境下的数据存取而生,典型成员:ConcurrentHashMapConcurrentLinkedQueueCopyOnWriteArrayList。它们提供线程安全操作,通常无需外部同步。

原子变量为基础变量(整型、长整型、引用)提供无锁的线程安全操作,常见:AtomicIntegerAtomicLongAtomicReference。在许多情况下可免去显式同步。

关于并发集合的高级用法与优化访问模式,请参见本章稍后**“利用线程安全集合缓解并发问题”**部分。

接下来,我们通过一个代码练习,看看如何实际运用 java.util.concurrent

动手实战——用 java.util.concurrent 实现并发应用

场景:实现一个基础的订单处理系统。订单被并行下单与处理,使用多种并发元素来进行同步、协调与保障数据一致性。代码如下:

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class OrderProcessingSystem {
    private final ExecutorService executorService = Executors.newFixedThreadPool(10);
    private final ConcurrentLinkedQueue<Order> orderQueue = new ConcurrentLinkedQueue<>();
    private final CopyOnWriteArrayList<Order> processedOrders = new CopyOnWriteArrayList<>();
    private final ConcurrentHashMap<Integer, String> orderStatus = new ConcurrentHashMap<>();
    private final Lock paymentLock = new ReentrantLock();
    private final Semaphore validationSemaphore = new Semaphore(5);
    private final AtomicInteger processedCount = new AtomicInteger(0);

    public void startProcessing() {
        while (!orderQueue.isEmpty()) {
            Order order = orderQueue.poll();
            executorService.submit(() -> processOrder(order));
        }
        executorService.close();
    }

    private void processOrder(Order order) {
        try {
            validateOrder(order);

            paymentLock.lock();
            try {
                processPayment(order);
            } finally {
                paymentLock.unlock();
            }

            shipOrder(order);
            processedOrders.add(order);
            processedCount.incrementAndGet();
            orderStatus.put(order.getId(), "Completed");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private void validateOrder(Order order) throws InterruptedException {
        validationSemaphore.acquire();
        try {
            Thread.sleep(100);
        } finally {
            validationSemaphore.release();
        }
    }

    private void processPayment(Order order) {
        System.out.println("Payment Processed for Order " + order.getId());
    }

    private void shipOrder(Order order) {
        System.out.println("Shipped Order " + order.getId());
    }

    public void placeOrder(Order order) {
        orderQueue.add(order);
        orderStatus.put(order.getId(), "Received");
        System.out.println("Order " + order.getId() + " placed.");
    }

    public static void main(String[] args) {
        OrderProcessingSystem system = new OrderProcessingSystem();
        for (int i = 0; i < 20; i++) {
            system.placeOrder(new Order(i));
        }
        system.startProcessing();
        System.out.println("All Orders Processed!");
    }

    static class Order {
        private final int id;
        public Order(int id) { this.id = id; }
        public int getId() { return id; }
    }
}

本例使用的并发要素:

  • ExecutorService:线程池并行处理订单任务。
  • ConcurrentLinkedQueue:线程安全队列,存放/管理待处理订单。
  • CopyOnWriteArrayList:适用于“读多写少”的已处理订单列表。
  • ConcurrentHashMap:高性能线程安全 Map,跟踪每个订单状态。
  • ReentrantLock:保护支付处理的关键区,避免并发问题。
  • Semaphore:限制并发验证数量,防止资源耗尽。
  • AtomicInteger:无锁计数,安全统计已处理订单数。

要点回顾:

  • 高效线程化:线程池并发处理多笔订单,潜在提升性能。
  • 同步协调:用锁与信号量保护共享资源与关键区,确保一致性、避免竞态。
  • 线程安全数据:借助线程安全集合支撑并发访问。
  • 状态追踪:维护订单状态,便于监控与上报。

该示例展示了如何将这些并发工具组合起来,构建一个多线程、已同步且可协调的订单处理应用:从任务并发到数据一致,再到线程间同步,各工具各尽其用。

接下来,我们将继续探讨 同步与锁机制 在 Java 应用中的使用方式。

使用 Future 与 Callable 执行“有结果”的异步任务

在 Java 中,FutureCallable 常配合使用:用于异步执行任务,并在“将来某个时间点”获取结果。

  • Callable 接口:表示可产生结果的任务(函数式接口)

    • call():封装任务逻辑,并返回结果
  • Future 接口:表示某个 Callable 任务最终完成(或失败)以及其关联结果

    • get():获取结果;如有需要会阻塞直到完成
    • isDone():检查任务是否已结束
  • 提交任务ExecutorService 接收 Callable,并返回 Future 以跟踪完成与获取结果

Callable / Future 示例:

ExecutorService executor = Executors.newFixedThreadPool(2);

Callable<Integer> task = () -> {
    // perform some computation
    return 42;
};

Future<Integer> future = executor.submit(task);

// do something else while the task is executing
Integer result = future.get(); // Retrieves the result, waiting if necessary

// Check if the task is completed
if (!future.isDone()) {
    System.out.println("Calculation is still in progress...");
}

executor.shutdown();

上面的示例中,Callable 定义会产生结果的任务Future 充当“把手”,用于管理并检索结果,从而实现异步协作携带结果的任务执行

关键点:

  • 异步执行Callable + Future 让任务独立于主线程运行,潜在提升性能
  • 结果获取:通过 Future 在结果可用时由主线程取回,保证必要的同步
  • 灵活协作Future 可用于依赖管理与构建更复杂的异步工作流

并发任务间的安全数据共享

不可变数据与**线程本地存储(TLS)**是并发编程的基础概念,能显著简化线程安全的实现。下面分别说明。

不可变数据(Immutable Data)

不可变数据指对象一旦创建,其状态不可改变;若要“修改”,会生成新对象,原对象保持不变。与此相对,可变数据可在创建后直接改变内部状态。

优势:

  • 无需同步:在多线程共享不可变数据时,不需要锁、信号量等同步机制
  • 增强线程安全:不可变性天然保证线程安全
  • 简化推理:无需担心他线程“悄悄改值”,代码更可预测、更易调试

示例类型:

  • 字符串:Java 的 String 是不可变的
  • 装箱基本类型:如 IntegerBoolean
  • 日期类:如 Java 8 的 LocalDate
  • final 类 + 不可变字段:自定义不可变类
  • 元组(Tuple) :常见于函数式语言,创建后不可更改。Java 无内建 Tuple,可用自定义类或第三方库模拟。

简单的 Tuple 示例:

public class Tuple<X, Y> {
    public final X first;
    public final Y second;
    public Tuple(X first, Y second) {
        this.first = first;
        this.second = second;
    }
    public static void main(String[] args) {
        // Creating a tuple of String and Integer
        Tuple<String, Integer> personAge = new Tuple<>("Joe", 30);
    }
}

线程本地存储(Thread-Local Storage,TLS)

TLS 指每个线程拥有一份私有数据存储,其他线程不可访问。

优势:

  • 简化共享:为每个线程提供独立数据,不用为协调访问而操心
  • 减少争用:数据隔离,降低锁竞争与瓶颈
  • 提升可维护性:逻辑清晰,便于理解与维护

常见用法:

  • 用户会话管理:在 Web 应用中保存用户特定数据
  • 计数器/临时变量:跟踪线程特定的计算
  • 缓存:存放高频、线程私有的数据以提速

对比与取舍:

  • 作用域:不可变数据保障的是数据本身在多线程共享下的安全;TLS 提供的是每线程独立的数据空间
  • 使用场景:不可变数据适合跨线程共享且只读的结构与值;TLS 适合仅属于某个线程、不应跨线程共享的数据

根据应用的需求与数据访问模式选择不可变数据或 TLS;合理结合两者,常能进一步提升并发系统的安全性与实现简洁度。

利用线程安全集合缓解并发问题

在已经了解并发集合与原子变量基础之后,下面聚焦如何进阶使用这些线程安全集合,以进一步缓解 Java 中的并发问题。

并发集合的高级用法:

  • 访问模式优化(Optimized access patterns) :针对多线程场景选择更契合的并发集合,实现常见并发访问模式(读写频繁、队列处理、读多写少等)的高效处理:

    • ConcurrentHashMap:适合高并发读写场景。可使用 computeIfAbsent原子复合操作,在检查与插入同时完成。
    • ConcurrentLinkedQueue:适合基于队列的数据处理模型,尤其是生产者-消费者模式。其无阻塞特性对高吞吐很关键。
    • CopyOnWriteArrayList:适合读多写少的列表。其迭代器提供稳定的快照视图,即使并发修改也能可靠迭代。
  • 与 Streams 结合:将 Java Streams 用于并行处理集合,尤其是配合 ConcurrentHashMap,可构建高效的并行算法

  • 策略性同步:即便使用线程安全集合,某些场景仍需额外同步——例如在遍历 ConcurrentHashMap 时执行多个相关且需要整体原子性的操作。

原子变量的优势(超越基础用法):

  • 复杂原子操作:利用 AtomicInteger/AtomicLongupdateAndGetaccumulateAndGet 等方法,将复杂计算压缩为单步原子更新。
  • 内存一致性保证:理解原子变量与并发集合提供的可见性语义。比如对 AtomicInteger 的操作对其他线程立即可见,这对确保数据最新可见性至关重要。

在并发集合与原子变量之间做选择

明确何时选用并发集合、何时使用原子变量,有助于构建高效、健壮且线程安全的 Java 应用。正确选择会显著影响并发程序的性能、可扩展性与可靠性。本节给出选择考量:

  • 数据复杂度:若要管理多元素/有关系的复杂数据结构,选择并发集合;若仅处理单一数值并需要原子操作,而不想引入集合开销,则选用原子变量。
  • 性能考量ConcurrentHashMap 在并发访问下具有良好可扩展性;而原子变量在简单用例中更轻量高效

深化对线程安全集合与原子变量的使用时机与方式的理解,可以在保证数据完整性的同时,最大化并发性能。

面向健壮应用的并发最佳实践

虽然本书第 5 章《掌握云计算中的并发模式》将专门讨论面向云环境的 Java 并发模式,但在此先奠定通用并发编程的基础策略:

  • 掌握并发原语:熟悉 synchronizedvolatileLockCondition 的语义与用法,写对并发代码的前提。
  • 最小化共享状态:尽量减少线程间共享数据;共享越多,复杂度与出错率越高。能不可变就不可变。
  • 正确处理中断:捕获 InterruptedException 后,务必调用 Thread.currentThread().interrupt() 恢复中断标志
  • 避免死锁:统一加锁顺序、获取锁时使用超时等手段降低死锁风险。
  • 使用高层并发工具:用 ExecutorServiceCountDownLatchCyclicBarrier 等管理线程与同步,避免“手工造轮子”。
  • 使用线程池:复用与管理线程,减少创建/销毁开销。
  • 优先无阻塞算法:在追求性能与扩展性场景,考虑基于原子类的无锁/非阻塞算法。
  • 谨慎延迟初始化:并发环境下的 Lazy 初始化易出错;若用双重检查锁(DCL),需配合 volatile 并正确实现。
  • 充分测试并发:在接近真实场景下进行压力/并发测试,检出线程安全、死锁、竞态等问题。
  • 文档化并发假设:明确记录并发相关假设与设计决策,帮助维护者理解策略。
  • 优化线程配置:将线程数量与负载、硬件能力匹配;过多线程会因频繁上下文切换而降速。
  • 监控与调优:持续监控并发应用性能,调优线程池规模、任务分片策略等。
  • 避免不必要阻塞:设计任务/算法时尽量避免让线程长期阻塞,优先选择能让线程独立前进的并发结构与算法。

以上最佳实践构成了健壮、高效、可维护的并发应用之基石,无论它们最终运行在何种领域(包括云计算)。

总结

当我们结束第 2 章时,不妨回顾一下在 Java 并发之旅中掌握的关键概念与最佳实践。本章总结就像大厨为一场成功宴席做的复盘,凝练出在 Java 中进行高效并发编程所必需的要点与策略。

我们首先理解了线程进程:线程好比灵巧的副厨,是执行的基本单位,在共享环境(同一“厨房”)中协同工作;进程则像彼此独立的厨房,各自拥有资源、相互隔离。我们梳理了线程的生命周期——从创建到终止——并强调了各关键阶段以及在 Java 运行时中的管理方式。

如同协调厨师团队,我们探讨了同步技术与加锁机制,它们对管理共享资源访问、预防冲突至关重要。随后我们直面死锁这一难题,学习如何在并发编程中检测与化解这种“僵持”,就像在繁忙厨房中疏通堵点。

接着,我们深入到更高级的工具,例如 StampedLock条件对象(Condition) ,为特定并发场景提供更精细的解决方案。

本章的一个核心部分,是面向健壮应用的并发最佳实践。这些实践好比专业厨房的“金科玉律”,确保效率、安全与质量。我们强调:理解并发模式、合理的资源管理,以及谨慎使用同步技术,是构建健壮且具韧性的 Java 应用的关键。

此外,通过动手练习真实案例,我们把这些理念落到实处,强化了对不同同步策略与加锁机制何时、如何有效使用的理解。

本章为你提供了应对并发复杂性的工具与方法。如今,你已具备在多线程世界中构建健壮、可扩展应用的能力。不过,我们的“烹饪之旅”尚未结束!在第 3 章《精通 Java 并行计算》中,我们将登上并行处理的“宴会大厅”,学习如何驾驭多核,释放更强大的 Java 魔力。请准备好将并发所学升华,用并行编程解锁真正的性能潜能。