java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java多线程机制

Java多线程核心机制全面解析

作者:故渊ZY

本文全面剖析了Java多线程的设计与应用,从多线程的本质、生命周期、核心机制到实战最佳实践,帮助读者掌握并发编程的核心能力,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

Java 多线程简介

在计算机硬件进入多核时代后,单线程程序已无法充分利用 CPU 资源。多线程(Multithreading)作为并发编程的核心技术,允许程序在同一进程内同时执行多个任务,是提升程序吞吐量、降低响应延迟的关键。从简单的后台任务到复杂的分布式系统,多线程无处不在。本文将从多线程的本质、生命周期、核心机制到实战最佳实践,全面剖析 Java 多线程的设计与应用,帮你掌握并发编程的核心能力。

一、多线程的本质:为什么需要多线程?

1. 多线程与进程、线程的关系

要理解多线程,首先需要明确进程(Process) 与线程(Thread) 的区别与联系 —— 二者是操作系统中调度和资源分配的基本单位,但定位不同:

对比维度进程(Process)线程(Thread)
资源分配操作系统资源分配的基本单位(独立内存空间、文件句柄)进程内的执行单元,共享进程资源(堆、方法区),仅拥有独立栈和程序计数器
切换开销高(需切换内存映射、寄存器状态,耗时约 10-100 微秒)低(仅切换栈和程序计数器,耗时约 1-10 微秒)
通信方式复杂(需通过 IPC:管道、socket、共享内存)简单(直接访问共享变量,需同步控制)
数量限制少(系统能同时运行的进程数通常不超过千级)多(单个进程可创建数千个线程)

形象类比:进程是 “工厂”,拥有独立的厂房(内存空间)和设备(文件句柄);线程是 “工厂里的工人”,共享厂房和设备,同时有自己的工具(栈空间),多个工人可同时处理不同任务(并发执行)。

多线程的核心价值在于 **“在单个进程内实现并发”**—— 无需创建多个独立进程,即可利用多核 CPU 同时执行任务,大幅降低资源开销和通信成本。

2. 多线程解决的核心问题

多线程并非 “银弹”,但能高效解决两类典型问题,这也是它被广泛应用的原因:

(1)CPU 密集型任务:充分利用多核 CPU

CPU 密集型任务(如数据计算、排序、加密)的核心瓶颈是 CPU 算力。单线程只能利用一个 CPU 核心,而多线程可将任务拆分到多个核心并行执行,减少总耗时。示例:用 4 核 CPU 处理 4 个排序任务,单线程需 4 秒(串行执行),多线程仅需 1 秒(并行执行),理论效率提升 300%。

(2)IO 密集型任务:减少 CPU 空闲时间

IO 密集型任务(如网络请求、文件读写、数据库操作)的核心瓶颈是 IO 等待(CPU 等待数据就绪,如等待网络响应、磁盘 IO 完成)。此时 CPU 处于空闲状态,多线程可让 CPU 在 IO 等待期间处理其他任务,提升 CPU 利用率。示例:单线程处理 10 个网络请求(每个请求耗时 1 秒,其中 0.8 秒是 IO 等待),总耗时 10 秒;多线程处理时,CPU 在 IO 等待期间切换线程,总耗时可降至 1 秒左右,CPU 利用率从 20% 提升至 100%。

3. Java 多线程的特殊性

Java 多线程是内核级线程(1:1 映射模型),即每个 Java 线程对应一个操作系统内核线程:

二、Java 多线程的生命周期:6 种状态与状态转换

Java 线程的生命周期被严格定义在java.lang.Thread.State枚举中,包含 6 种状态。线程在不同状态间的转换遵循固定规则,是理解线程调度和并发控制的基础。

1. 6 种核心状态及含义

状态名称核心含义触发场景
NEW(新建)线程对象已创建(如new Thread()),但未调用start(),未与内核线程关联。Thread thread = new Thread();
**RUNNABLE(可运行)线程已调用start(),分为 “就绪”(等待 CPU 调度)和 “运行中”(占用 CPU)两种子状态,JVM 不区分这两种子状态。thread.start();后,线程等待 CPU 调度或正在执行run()方法
**BLOCKED(阻塞)线程等待获取synchronized锁(进入同步块 / 方法时未抢到锁),释放 CPU,等待锁释放。线程 A 执行synchronized方法时,线程 B 尝试进入该方法
**WAITING(无限等待)线程通过wait()join()LockSupport.park()主动放弃 CPU,无限期等待其他线程唤醒。object.wait()(未指定超时)、thread.join()LockSupport.park()
**TIMED_WAITING(计时等待)线程通过wait(long)sleep(long)等方法放弃 CPU,等待指定时间后自动唤醒,或被其他线程提前唤醒。Thread.sleep(1000)object.wait(500)LockSupport.parkNanos(1_000_000)
**TERMINATED(终止)线程执行完毕(run()方法正常返回)或因未捕获异常退出,生命周期结束,无法再恢复。run()方法执行完毕、线程抛出RuntimeException且未捕获

2. 状态转换流程图(核心路径)

NEW → RUNNABLE:调用thread.start()(仅能触发一次,再次调用会抛IllegalThreadStateException)
RUNNABLE → BLOCKED:尝试获取synchronized锁失败
BLOCKED → RUNNABLE:其他线程释放synchronized锁,当前线程抢到锁
RUNNABLE → WAITING:调用object.wait()、thread.join()、LockSupport.park()
WAITING → RUNNABLE:其他线程调用object.notify()/notifyAll()、LockSupport.unpark()
RUNNABLE → TIMED_WAITING:调用Thread.sleep(long)、object.wait(long)、thread.join(long)
TIMED_WAITING → RUNNABLE:等待时间到、其他线程调用notify()/unpark()
RUNNABLE → TERMINATED:run()执行完毕、未捕获异常终止

关键注意点

三、Java 多线程的创建方式:4 种核心方法对比

Java 提供了多种创建线程的方式,不同方式在 “灵活性”“返回值”“异常处理” 上存在差异,需根据场景选择。

1. 方式 1:继承 Thread 类(重写 run ())

最基础的创建方式,通过继承Thread类并重写run()方法定义线程任务。

// 1. 继承Thread类
class MyThread extends Thread {
    // 2. 重写run():定义线程执行的任务(无返回值,不能抛受检异常)
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            // Thread.currentThread():获取当前执行的线程对象
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(500); // 模拟任务耗时,线程进入TIMED_WAITING状态
            } catch (InterruptedException e) {
                // 捕获中断异常(如其他线程调用interrupt())
                System.out.println(Thread.currentThread().getName() + "被中断");
                return; // 中断后退出线程
            }
        }
    }
}
// 使用线程
public class ThreadCreate1 {
    public static void main(String[] args) {
        // 3. 创建线程对象
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        // 4. 设置线程名(便于调试)
        thread1.setName("线程A");
        thread2.setName("线程B");
        // 5. 启动线程(状态从NEW→RUNNABLE)
        thread1.start();
        thread2.start();
        // 注意:直接调用run()不会创建新线程,仅在主线程中执行
        // thread1.run(); // 错误用法
    }
}

输出结果(并发执行,顺序不固定)

线程A: 0
线程B: 0
线程A: 1
线程B: 1
线程A: 2
线程B: 2
...

优缺点

2. 方式 2:实现 Runnable 接口(推荐)

通过实现Runnable接口的run()方法定义任务,再将Runnable对象传入Thread构造器,避免单继承限制,是更灵活的方式。

// 1. 实现Runnable接口
class MyRunnable implements Runnable {
    // 2. 重写run():定义任务(无返回值,不能抛受检异常)
    @Override
    public 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 ThreadCreate2 {
    public static void main(String[] args) {
        // 3. 创建任务对象(任务与线程分离)
        MyRunnable task = new MyRunnable();
        // 4. 将任务传入Thread,启动线程(多个线程可共享同一个任务)
        Thread thread1 = new Thread(task, "线程C");
        Thread thread2 = new Thread(task, "线程D");
        thread1.start();
        thread2.start();
    }
}

核心优势

3. 方式 3:实现 Callable 接口(带返回值)

Runnable的增强版,支持线程执行后返回结果(通过Future获取)和抛出受检异常,适用于需要获取任务结果的场景(如异步计算、耗时任务)。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// 1. 实现Callable接口(泛型指定返回值类型)
class MyCallable implements Callable<Integer> {
    private int num;
    public MyCallable(int num) {
        this.num = num;
    }
    // 2. 重写call():有返回值,可抛受检异常
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= num; i++) {
            sum += i;
            Thread.sleep(100); // 模拟计算耗时
        }
        return sum; // 返回1~num的和
    }
}
// 使用线程
public class ThreadCreate3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 3. 创建Callable任务对象
        MyCallable task = new MyCallable(10);
        // 4. 用FutureTask包装Callable(FutureTask实现了Runnable,可传入Thread)
        FutureTask<Integer> futureTask = new FutureTask<>(task);
        // 5. 启动线程
        new Thread(futureTask, "计算线程").start();
        // 6. 获取任务结果(get()是阻塞方法,直到线程执行完毕返回结果)
        System.out.println("主线程等待计算结果...");
        Integer result = futureTask.get(); // 阻塞等待,直到call()返回
        System.out.println("1~10的和:" + result); // 输出:55
        // 其他FutureTask方法
        System.out.println("任务是否完成:" + futureTask.isDone()); // true
        System.out.println("任务是否取消:" + futureTask.isCancelled()); // false
    }
}

核心特性

4. 方式 4:线程池(ExecutorService,实战首选)

通过java.util.concurrent.ExecutorService线程池创建线程,避免频繁创建 / 销毁线程的开销(线程池复用线程),是企业级开发的首选方式。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadPoolCreate {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1. 创建线程池(根据场景选择线程池类型)
        // 固定线程数线程池(适合CPU密集型任务)
        ExecutorService fixedPool = Executors.newFixedThreadPool(2);
        // 缓存线程池(适合IO密集型任务,线程空闲60秒后销毁)
        // ExecutorService cachedPool = Executors.newCachedThreadPool();
        // 单线程池(适合顺序执行任务)
        // ExecutorService singlePool = Executors.newSingleThreadExecutor();
        // 2. 提交Runnable任务(无返回值)
        fixedPool.submit(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + ": Runnable任务");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 3. 提交Callable任务(有返回值,返回Future对象)
        Future<Integer> future = fixedPool.submit(() -> {
            int sum = 0;
            for (int i = 1; i <= 5; i++) {
                sum += i;
                Thread.sleep(100);
            }
            return sum;
        });
        // 4. 获取Callable任务结果
        System.out.println("Callable任务结果:" + future.get()); // 输出:15
        // 5. 关闭线程池(重要!否则JVM不会退出)
        // shutdown():不再接受新任务,等待已提交任务执行完毕后关闭
        fixedPool.shutdown();
        // shutdownNow():立即关闭,尝试中断正在执行的任务(可能导致数据不一致)
        // fixedPool.shutdownNow();
    }
}

线程池的核心优势

线程池选择建议

任务类型推荐线程池类型核心参数设置(参考)
CPU 密集型(计算)newFixedThreadPool(n)n = CPU 核心数(Runtime.getRuntime ().availableProcessors ())
IO 密集型(网络 / 文件)newCachedThreadPool()线程数随任务增长,空闲 60 秒销毁
定时 / 延迟任务newScheduledThreadPool(n)n = 2~4(根据定时任务数量调整)
顺序执行任务newSingleThreadExecutor()仅 1 个线程,保证任务按提交顺序执行

四、多线程的核心挑战:线程安全与同步控制

多线程并发执行时,若多个线程操作共享资源(如共享变量、文件、数据库连接),会出现 “线程安全问题”(数据竞争、结果不一致)。解决线程安全的核心是同步控制—— 确保同一时间只有一个线程操作共享资源。

1. 线程安全问题的根源:可见性、原子性、有序性

Java 内存模型(JMM)定义了线程与主内存之间的交互规则,多线程安全问题本质是 JMM 的三大特性被破坏:

(1)可见性:一个线程修改的共享变量,其他线程无法立即看到
class VisibilityDemo {
    private boolean stopFlag = false; // 共享变量(无可见性保证)
    public void start() {
        new Thread(() -> {
            while (!stopFlag) { // 线程1读取的是CPU缓存中的false,即使主线程修改也无法看到
                // 循环执行
            }
            System.out.println("线程停止");
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stopFlag = true; // 主线程修改stopFlag为true,但线程1无法看到
        System.out.println("主线程已设置stopFlag为true");
    }
    public static void main(String[] args) {
        new VisibilityDemo().start(); // 线程1不会停止,陷入无限循环
    }
}
(2)原子性:一个操作不可分割,要么全部执行,要么全部不执行
class AtomicityDemo {
    private int count = 0; // 共享变量(无原子性保证)
    // 非线程安全的方法
    public void increment() {
        count++; // 原子性问题:多线程下操作交错,导致计数不准确
    }
    public int getCount() {
        return count;
    }
    public static void main(String[] args) throws InterruptedException {
        AtomicityDemo demo = new AtomicityDemo();
        int threadNum = 2;
        Thread[] threads = new Thread[threadNum];
        // 2个线程各执行1000次increment()
        for (int i = 0; i < threadNum; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    demo.increment();
                }
            });
            threads[i].start();
        }
        // 等待所有线程执行完毕
        for (Thread thread : threads) {
            thread.join();
        }
        // 预期结果2000,实际结果可能小于2000(如1850)
        System.out.println("最终计数:" + demo.getCount());
    }
}
(3)有序性:程序执行顺序与代码顺序不一致
class OrderingDemo {
    private int a = 0;
    private boolean flag = false;
    // 线程1执行
    public void writer() {
        a = 1; // 指令1
        flag = true; // 指令2(可能被重排序到指令1之前)
    }
    // 线程2执行
    public void reader() {
        if (flag) { // 若flag=true,线程2认为a已被赋值为1
            System.out.println(a); // 可能输出0(指令2先执行,指令1未执行)
        }
    }
}

2. 解决线程安全:3 种核心同步机制

(1)synchronized 关键字(内置锁,JVM 层面)

synchronized是 Java 内置的同步机制,通过 “对象监视器(Object Monitor)” 实现,保证 “同一时间只有一个线程执行同步代码块 / 方法”,可同时解决可见性、原子性、有序性问题。

使用方式

同步方法:锁对象是this(非静态方法)或 “类对象”(静态方法);

// 非静态同步方法(锁是this)
public synchronized void safeIncrement() {
    count++; // 原子操作
}
// 静态同步方法(锁是AtomicityDemo.class)
public static synchronized void staticSafeIncrement() {
    staticCount++;
}

同步代码块:锁对象可自定义(推荐,缩小同步范围,提升性能);

public void safeIncrement() {
    synchronized (this) { // 锁对象为this(可替换为其他对象,如private final Object lock = new Object())
        count++;
    }
}

原理

优缺点

(2)Lock 接口(显式锁,JDK 层面)

java.util.concurrent.locks.Lock接口是synchronized的增强版,支持更灵活的锁操作(可中断、超时、公平锁),核心实现类是ReentrantLock(可重入锁)。

使用方式

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class LockDemo {
    private int count = 0;
    // 创建Lock对象(默认非公平锁,传入true创建公平锁)
    private final Lock lock = new ReentrantLock(true);
    public void safeIncrement() {
        lock.lock(); // 加锁(若锁被占用,线程进入WAITING状态,加入条件队列)
        try {
            count++; // 临界区代码(原子操作)
        } finally {
            lock.unlock(); // 释放锁(必须在finally中,避免异常导致锁未释放)
        }
    }
    // 支持中断的加锁(避免线程无限等待)
    public void safeIncrementWithInterrupt() throws InterruptedException {
        if (lock.tryLock(1000, java.util.concurrent.TimeUnit.MILLISECONDS)) { // 超时1秒
            try {
                count++;
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("获取锁超时");
        }
    }
    public int getCount() {
        return count;
    }
}

核心特性

Lock vs synchronized

特性synchronizedReentrantLock
锁获取 / 释放隐式(JVM 自动管理)显式(手动 lock ()/unlock ())
可中断性不可中断(BLOCKED 状态无法中断)可中断(lockInterruptibly ()/tryLock ())
超时获取不支持支持(tryLock (long))
公平锁不支持(非公平)支持(构造器传入 true)
条件变量支持(wait ()/notify (),1 个条件队列)支持(Condition,多个条件队列)
锁状态查询不支持支持(isLocked ()/hasQueuedThreads ())
(3)原子类(AtomicXXX,无锁机制)

java.util.concurrent.atomic包下的原子类,基于CAS(Compare and Swap,比较并交换) 实现无锁同步,无需加锁即可保证原子性,性能优于synchronizedLock(无锁竞争开销)。

核心原子类

使用方式

import java.util.concurrent.atomic.AtomicInteger;
class AtomicDemo {
    // 原子类:保证count++是原子操作
    private final AtomicInteger count = new AtomicInteger(0);
    public void safeIncrement() {
        count.incrementAndGet(); // 原子性的count++(等价于count = count + 1)
    }
    // 其他原子操作
    public void add() {
        count.addAndGet(5); // 原子性的count += 5
    }
    public int getCount() {
        return count.get();
    }
    public static void main(String[] args) throws InterruptedException {
        AtomicDemo demo = new AtomicDemo();
        Thread[] threads = new Thread[2];
        for (int i = 0; i < 2; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    demo.safeIncrement();
                }
            });
            threads[i].start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
        System.out.println("最终计数:" + demo.getCount()); // 输出:2000(准确)
    }
}

CAS 原理:CAS 是一种无锁算法,包含 3 个参数:

执行逻辑:仅当主内存中 V 的值等于 A 时,才将 V 的值更新为 B,否则不操作;整个过程由 CPU 指令cmpxchg保证原子性(无需加锁)。

优缺点

3. 线程通信:多线程协作的核心机制

线程通信是指多个线程通过共享变量或特定 API 协调执行顺序(如 “线程 A 执行完任务 1 后,线程 B 再执行任务 2”),Java 提供 3 种核心通信方式:

(1)wait ()/notify ()/notifyAll ()(基于 synchronized)

这三个方法是Object类的成员方法,必须在synchronized同步块 / 方法中使用,依赖 “对象监视器” 的等待队列和同步队列实现通信。

示例:生产者 - 消费者模型(单生产者单消费者)

// 共享缓冲区(存储产品)
class Buffer {
    private int[] products = new int[5]; // 缓冲区大小为5
    private int count = 0; // 当前产品数量
    private int putIndex = 0; // 生产者放入产品的索引
    private int takeIndex = 0; // 消费者取出产品的索引
    // 生产者放入产品
    public synchronized void put(int product) throws InterruptedException {
        // 缓冲区满,等待消费者取出(用while避免虚假唤醒)
        while (count == products.length) {
            System.out.println("缓冲区满,生产者等待");
            wait(); // 释放锁,进入等待队列
        }
        // 放入产品
        products[putIndex] = product;
        putIndex = (putIndex + 1) % products.length;
        count++;
        System.out.println("生产者放入产品:" + product + ",当前数量:" + count);
        notify(); // 唤醒消费者(缓冲区有产品了)
    }
    // 消费者取出产品
    public synchronized void take() throws InterruptedException {
        // 缓冲区空,等待生产者放入
        while (count == 0) {
            System.out.println("缓冲区空,消费者等待");
            wait(); // 释放锁,进入等待队列
        }
        // 取出产品
        int product = products[takeIndex];
        takeIndex = (takeIndex + 1) % products.length;
        count--;
        System.out.println("消费者取出产品:" + product + ",当前数量:" + count);
        notify(); // 唤醒生产者(缓冲区有空位了)
    }
}
// 生产者线程
class Producer extends Thread {
    private Buffer buffer;
    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            try {
                buffer.put(i);
                Thread.sleep(500); // 模拟生产耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
// 消费者线程
class Consumer extends Thread {
    private Buffer buffer;
    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            try {
                buffer.take();
                Thread.sleep(800); // 模拟消费耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
// 测试
public class WaitNotifyDemo {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        new Producer(buffer).start();
        new Consumer(buffer).start();
    }
}

输出结果(片段)

生产者放入产品:1,当前数量:1
消费者取出产品:1,当前数量:0
缓冲区空,消费者等待
生产者放入产品:2,当前数量:1
消费者取出产品:2,当前数量:0
缓冲区空,消费者等待
生产者放入产品:3,当前数量:1
生产者放入产品:4,当前数量:2
消费者取出产品:3,当前数量:1
...

关键注意点:用while循环判断条件(而非if),避免 “虚假唤醒”—— 线程可能在未被notify()的情况下被唤醒(如操作系统信号中断),while循环会重新检查条件,确保逻辑正确。

(2)Condition 接口(基于 Lock)

ConditionLock的配套通信工具,通过Lock.newCondition()创建,功能与wait()/notify()类似,但支持更精细的线程分组通信(一个Lock可创建多个Condition,实现不同线程组的独立唤醒)。

示例:用 Condition 实现生产者 - 消费者(多生产者多消费者)

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ConditionBuffer {
    private int[] products = new int[5];
    private int count = 0;
    private int putIndex = 0;
    private int takeIndex = 0;
    private final Lock lock = new ReentrantLock();
    // 创建两个Condition:生产者等待队列和消费者等待队列
    private final Condition producerCond = lock.newCondition();
    private final Condition consumerCond = lock.newCondition();
    public void put(int product) throws InterruptedException {
        lock.lock();
        try {
            while (count == products.length) {
                System.out.println("缓冲区满,生产者等待");
                producerCond.await(); // 生产者进入生产者等待队列
            }
            products[putIndex] = product;
            putIndex = (putIndex + 1) % products.length;
            count++;
            System.out.println("生产者放入产品:" + product + ",当前数量:" + count);
            consumerCond.signal(); // 仅唤醒消费者等待队列的线程(精准唤醒)
        } finally {
            lock.unlock();
        }
    }
    public int take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                System.out.println("缓冲区空,消费者等待");
                consumerCond.await(); // 消费者进入消费者等待队列
            }
            int product = products[takeIndex];
            takeIndex = (takeIndex + 1) % products.length;
            count--;
            System.out.println("消费者取出产品:" + product + ",当前数量:" + count);
            producerCond.signal(); // 仅唤醒生产者等待队列的线程
            return product;
        } finally {
            lock.unlock();
        }
    }
}

优势synchronizedwait()/notify()只能唤醒 “所有线程” 或 “一个随机线程”,而Condition可通过多个条件队列实现 “精准唤醒”(如只唤醒生产者或只唤醒消费者),减少不必要的锁竞争,提升并发性能。

(3)JUC 工具类(CountDownLatch/CyclicBarrier/Semaphore)

java.util.concurrent包提供了更高级的线程通信工具类,简化复杂的协作场景:

import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        int taskNum = 3;
        CountDownLatch latch = new CountDownLatch(taskNum);
        for (int i = 0; i < taskNum; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "执行任务");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown(); // 任务完成,倒计时减1
                }
            }, "任务线程" + i).start();
        }
        latch.await(); // 主线程等待,直到倒计时为0
        System.out.println("所有任务执行完毕,主线程继续");
    }
}
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        int threadNum = 3;
        // 屏障动作:所有线程到达后执行
        CyclicBarrier barrier = new CyclicBarrier(threadNum, () -> {
            System.out.println("所有线程到达屏障,执行屏障动作");
        });
        for (int i = 0; i < threadNum; i++) {
            new Thread(() -> {
                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 (Exception e) {
                    e.printStackTrace();
                }
            }, "线程" + i).start();
        }
    }
}
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
    public static void main(String[] args) {
        int permitNum = 2; // 允许同时访问的线程数
        Semaphore semaphore = new Semaphore(permitNum);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire(); // 获取许可(若无许可,线程阻塞)
                    System.out.println(Thread.currentThread().getName() + "获取许可,访问资源");
                    Thread.sleep(1000); // 模拟访问资源耗时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // 释放许可
                    System.out.println(Thread.currentThread().getName() + "释放许可");
                }
            }, "线程" + i).start();
        }
    }
}

五、多线程的实战最佳实践:避坑与优化

1. 优先使用线程池,避免手动创建线程

2. 避免线程安全问题的核心原则

3. 优化锁性能的关键技巧

// 优化前:同步整个方法
public synchronized void process() {
    // 非临界区代码(无需同步)
    log();
    // 临界区代码(需同步)
    count++;
}
// 优化后:仅同步临界区
public void process() {
    // 非临界区代码(无需同步)
    log();
    synchronized (this) {
        // 临界区代码(需同步)
        count++;
    }
}
import java.util.concurrent.locks.ReentrantReadWriteLock;
class ReadWriteLockDemo {
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
    private int data;
    // 读操作(共享)
    public int readData() {
        readLock.lock();
        try {
            return data;
        } finally {
            readLock.unlock();
        }
    }
    // 写操作(排他)
    public void writeData(int data) {
        writeLock.lock();
        try {
            this.data = data;
        } finally {
            writeLock.unlock();
        }
    }
}

4. 正确处理线程中断

线程中断是线程间的 “协作信号”,表示 “希望你尽快终止”,而非强制终止线程,正确处理方式:

public void run() {
    while (!Thread.currentThread().isInterrupted()) {
        // 执行任务逻辑
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // 恢复中断状态(否则后续循环无法感知中断)
            Thread.currentThread().interrupt();
            break; // 退出循环,终止线程
        }
    }
}

5. 避免 ThreadLocal 泄漏

ThreadLocal用于存储线程私有变量(每个线程有独立副本),但使用不当会导致内存泄漏:

public class ThreadLocalDemo {
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public void process() {
        try {
            threadLocal.set("线程私有数据");
            // 使用数据...
        } finally {
            threadLocal.remove(); // 清除value,避免泄漏
        }
    }
}

六、常见面试题与误区解析

1. 高频面试题

2. 典型误区

总结

Java 多线程是并发编程的基石,理解线程的生命周期、同步与通信机制,是写出高效、安全的并发代码的前提。从简单的Thread创建到复杂的线程池管理,从synchronized的基础同步到CAS的无锁优化,每一种机制都对应特定的问题场景。

在实际开发中,无需过度追求 “复杂的并发技巧”,而应遵循 “简单优先” 原则:能用单线程解决的问题,不滥用多线程;能通过原子类或线程池解决的问题,不手动实现复杂的锁逻辑。只有在理解底层原理的基础上,才能灵活应对各种并发场景,让多线程成为提升程序性能的工具,而非引发问题的隐患。

多线程的核心价值,在于让程序 “更高效地利用资源”—— 无论是多核 CPU 的算力,还是 IO 等待时的 CPU 空闲时间。掌握多线程,不仅是掌握一种技术,更是理解 “如何在动态变化的环境中协调多个任务”,这正是优秀工程师的核心能力之一。

到此这篇关于Java多线程核心机制全解析的文章就介绍到这了,更多相关java多线程机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文