Java 多线程终极教程

目录

  1. 第一章:为什么需要多线程?

    多线程 java 教程
    (图片来源网络,侵删)
    • 1 什么是线程?
    • 2 多线程的优势
    • 3 多线程的挑战
  2. 第二章:Java 线程基础

    • 1 Thread
    • 2 Runnable 接口
    • 3 CallableFuture (有返回值的线程)
    • 4 继承 Thread vs 实现 Runnable
  3. 第三章:线程的生命周期与状态

    • 1 六种线程状态
    • 2 状态转换图
  4. 第四章:线程的核心:synchronized 与锁

    • 1 为什么需要同步?—— synchronized 的原理
    • 2 synchronized 关键字的三种用法
    • 3 Lock 接口与 ReentrantLock
    • 4 synchronized vs Lock
  5. 第五章:线程间的通信与协作

    多线程 java 教程
    (图片来源网络,侵删)
    • 1 wait(), notify(), notifyAll()
    • 2 join() 方法
    • 3 volatile 关键字
  6. 第六章:高级并发工具

    • 1 线程池 (Executor 框架)
    • 2 CountDownLatch (倒计时门闩)
    • 3 CyclicBarrier (循环栅栏)
    • 4 Semaphore (信号量)
    • 5 BlockingQueue (阻塞队列)
  7. 第七章:原子类与无锁编程

    • 1 java.util.concurrent.atomic
    • 2 CAS (Compare-And-Swap) 原理
  8. 第八章:Java 内存模型

    • 1 JMM 是什么?
    • 2 可见性、有序性与原子性
    • 3 happens-before 原则
  9. 第九章:最佳实践与常见陷阱

    • 1 最佳实践
    • 2 常见陷阱 (死锁、活锁、饥饿)
  10. 总结与学习路径


第一章:为什么需要多线程?

1 什么是线程?

进程 是操作系统进行资源分配和调度的基本单位,它拥有独立的内存空间和系统资源,一个应用程序可以启动多个进程。

线程 是进程中的一个执行单元,是 CPU 调度的基本单位,一个进程可以包含多个线程,它们共享该进程的内存空间和系统资源。

比喻:

  • 进程 就像一个工厂。
  • 线程 就像工厂里的工人,工人(线程)共享工厂的资源(内存),但每个工人可以同时独立地完成自己的任务。

2 多线程的优势

  1. 提高 CPU 利用率:当一个线程因为 I/O 操作(如读写文件、网络请求)而阻塞时,CPU 可以切换到其他就绪的线程去执行,而不是空闲等待。
  2. 提高程序响应速度:对于有图形界面的应用,可以将耗时操作放在后台线程执行,避免界面卡顿。
  3. 简化程序模型:对于某些复杂任务,可以将其分解为多个独立的子任务,每个子任务由一个线程处理,使程序结构更清晰。

3 多线程的挑战

多线程在带来便利的同时,也引入了复杂的问题:

  • 线程安全问题:当多个线程同时读写共享数据时,可能会导致数据不一致或损坏,这是最核心、最常见的问题。
  • 死锁:两个或多个线程互相等待对方释放资源,导致所有线程都无法继续执行。
  • 上下文切换开销:CPU 在不同线程之间切换需要保存和恢复线程的上下文,这会带来一定的性能开销。
  • 复杂性增加:多线程程序比单线程程序更难设计、调试和维护。

第二章:Java 线程基础

创建线程在 Java 中主要有三种方式。

1 Thread

Thread 类是 Java 中对线程的抽象,我们可以通过继承 Thread 类并重写其 run() 方法来创建一个线程。

示例代码:

// 1. 继承 Thread 类
class MyThread extends Thread {
    @Override
    public void run() {
        // 线程要执行的任务
        for (int i = 0; i < 5; i++) {
            System.out.println("Thread " + Thread.currentThread().getName() + " is running, i = " + i);
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        // 2. 创建线程对象
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        // 3. 启动线程
        thread1.start(); // 注意:是调用 start() 方法,而不是 run()
        thread2.start();
    }
}

重要提示:调用 thread.start() 会告诉 JVM 创建一个新的线程,并让这个新线程去执行 run() 方法,如果直接调用 thread.run(),则只是在当前线程中执行了 run() 方法,并没有创建新线程。

2 Runnable 接口

实现 Runnable 接口是更推荐的方式,因为它避免了 Java 单继承的限制。

示例代码:

// 1. 实现 Runnable 接口
class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程要执行的任务
        for (int i = 0; i < 5; i++) {
            System.out.println("Runnable " + Thread.currentThread().getName() + " is running, i = " + i);
        }
    }
}
public class RunnableDemo {
    public static void main(String[] args) {
        // 2. 创建 Runnable 实现类对象
        MyRunnable myRunnable = new MyRunnable();
        // 3. 创建 Thread 对象,并将 Runnable 对象作为构造参数传入
        Thread thread1 = new Thread(myRunnable, "Thread-A");
        Thread thread2 = new Thread(myRunnable, "Thread-B");
        // 4. 启动线程
        thread1.start();
        thread2.start();
    }
}

3 CallableFuture (有返回值的线程)

Runnablerun() 方法不能返回结果,也不能抛出受检异常,如果需要线程返回结果,可以使用 Callable 接口。

Callable 是一个泛型接口,其 call() 方法有返回值。

Future 接口代表一个异步计算的未来结果,可以用来检查计算是否完成、获取计算结果或取消计算。

示例代码:

import java.util.concurrent.*;
class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum += i;
        }
        // 模拟耗时操作
        Thread.sleep(2000);
        return sum;
    }
}
public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1. 创建线程池
        ExecutorService executor = Executors.newSingleThreadExecutor();
        // 2. 提交 Callable 任务,并获取 Future 对象
        Future<Integer> future = executor.submit(new MyCallable());
        // 3. 可以做其他事情...
        // 4. 获取结果(此方法会阻塞,直到计算完成)
        Integer result = future.get();
        System.out.println("计算结果是: " + result);
        // 5. 关闭线程池
        executor.shutdown();
    }
}

4 继承 Thread vs 实现 Runnable

特性 继承 Thread 实现 Runnable / Callable
继承 单继承,无法再继承其他类 可以继承其他类,实现多个接口
共享资源 不易于共享(除非静态变量) 非常方便,只需将同一个 Runnable 实例传给多个 Thread
面向接口 面向对象,设计上不够灵活 符合“面向接口编程”的设计思想,更灵活
推荐度 不推荐 强烈推荐

第三章:线程的生命周期与状态

Java 线程在其生命周期中会经历多种状态,在 Thread 类中定义了六种状态:

  1. NEW (新建):线程被创建,但尚未调用 start() 方法。
  2. RUNNABLE (可运行):线程调用了 start() 方法,正在等待 CPU 时间片,或者正在执行,在操作系统中,它可能处于“就绪”或“运行”状态,但在 Java API 层面,它们被统一称为 RUNNABLE
  3. BLOCKED (阻塞):线程因为等待监视器锁(即 synchronized 锁)而进入阻塞状态。
  4. WAITING (等待):线程因为调用了 wait(), join()LockSupport.park() 方法而无限期等待,直到其他线程唤醒它。
  5. TIMED_WAITING (超时等待):与 WAITING 类似,但它可以在指定时间后自动唤醒,例如调用了 sleep(long), wait(long), join(long) 等。
  6. TERMINATED (终止):线程已经执行完毕或因异常退出。

1 状态转换图

+----------------+     +------------------+
|      NEW       | --> |     RUNNABLE     |
+----------------+     +------------------+
       |                      |
       | start()              | 获取CPU时间片
       v                      v
+----------------+     +------------------+
|  TERMINATED    | <-- |      RUNNING     |
+----------------+     +------------------+
       ^                      |
       | 执行完毕/异常          | I/O阻塞 / 等待锁
       |                      v
       |            +------------------+
       +-----------> |    BLOCKED      |
                     +------------------+
       ^                      |
       | 被唤醒 / 锁释放       | 超时 / 被唤醒
       |                      v
+----------------+     +------------------+
|  TIMED_WAITING | <-- |      WAITING     |
+----------------+     +------------------+

第四章:线程的核心:synchronized 与锁

1 为什么需要同步?—— synchronized 的原理

当多个线程同时访问共享资源(如一个变量)时,可能会发生竞态条件,导致数据不一致。

示例: 一个简单的计数器

class Counter {
    private int count = 0;
    public void increment() {
        count++; // 这不是原子操作!
    }
    public int getCount() {
        return count;
    }
}

count++ 实际上包含三个步骤:

  1. 读取 count 的值。
  2. 增加 1。
  3. 写入 新值回 count

如果两个线程 A 和 B 同时执行 increment(),它们可能在读取到同一个旧值(如 0)后,各自增加 1,然后都写回 1,导致结果为 1 而不是预期的 2。

synchronized 关键字可以确保代码块或方法在同一时间只有一个线程可以进入,从而保证了原子性

2 synchronized 关键字的三种用法

  1. 修饰实例方法:锁是当前对象实例(this)。

    public synchronized void increment() {
        count++;
    }
  2. 修饰静态方法:锁是当前类的 Class 对象。

    public static synchronized void doSomething() {
        // ...
    }
  3. 修饰代码块:可以指定锁对象,更灵活。

    public void increment() {
        synchronized (this) { // 锁是当前对象实例
            count++;
        }
    }
    public void anotherMethod() {
        synchronized (Counter.class) { // 锁是当前类的 Class 对象
            // ...
        }
    }

3 Lock 接口与 ReentrantLock

java.util.concurrent.locks.Lock 接口提供了比 synchronized 更广泛的锁定操作。ReentrantLockLock 接口最常用的实现。

synchronized vs ReentrantLock

特性 synchronized ReentrantLock
锁的获取 JVM 自动管理 手动 lock()unlock()
可中断性 不可中断 可以 lockInterruptibly(),可以被中断
公平性 非公平锁 可以指定是否为公平锁
条件变量 一个(wait/notify 多个 Condition 对象
灵活性 较低 高,可以实现更复杂的锁逻辑

ReentrantLock 使用示例:

import java.util.concurrent.locks.ReentrantLock;
class Counter {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();
    public void increment() {
        lock.lock(); // 加锁
        try {
            count++;
        } finally {
            lock.unlock(); // 确保锁被释放
        }
    }
    // 使用 tryLock 避免阻塞
    public boolean tryIncrement() {
        if (lock.tryLock()) {
            try {
                count++;
                return true;
            } finally {
                lock.unlock();
            }
        }
        return false;
    }
}

最佳实践:总是在 finally 块中释放锁,以确保锁一定会被释放,避免死锁。


第五章:线程间的通信与协作

1 wait(), notify(), notifyAll()

这三个方法用于在 synchronized 代码块或方法中实现线程间的等待和通知机制。

  • void wait():导致当前线程等待,直到其他线程调用此对象的 notify()notifyAll() 方法,释放当前持有的锁。
  • void notify():唤醒在此对象监视器上等待的单个线程,选择哪个线程是任意的。
  • void notifyAll():唤醒在此对象监视器上等待的所有线程。

经典生产者-消费者模型示例:

class Buffer {
    private int content;
    private boolean isEmpty = true;
    public synchronized void put(int value) throws InterruptedException {
        while (!isEmpty) { // 使用 while 而不是 if,防止虚假唤醒
            wait(); // 如果缓冲区不为空,生产者等待
        }
        this.content = value;
        isEmpty = false;
        System.out.println("生产者生产: " + value);
        notifyAll(); // 唤醒消费者
    }
    public synchronized int get() throws InterruptedException {
        while (isEmpty) { // 使用 while 而不是 if
            wait(); // 如果缓冲区为空,消费者等待
        }
        isEmpty = true;
        System.out.println("消费者消费: " + content);
        notifyAll(); // 唤醒生产者
        return content;
    }
}

注意

  • 必须在 synchronized 上下文中使用这三个方法。
  • 建议使用 while 循环来检查条件,而不是 if,以应对“虚假唤醒”(Spurious Wakeup)的情况。

2 join() 方法

join() 方法的作用是等待调用该方法的线程执行完毕。

public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread threadB = new Thread(() -> {
            try {
                Thread.sleep(1000);
                System.out.println("线程B执行完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadB.start();
        System.out.println("主线程等待线程B...");
        threadB.join(); // 主线程会在这里阻塞,直到 threadB 执行完毕
        System.out.println("主线程继续执行");
    }
}

3 volatile 关键字

volatile 关键字可以保证可见性有序性,但不能保证原子性。

  • 可见性:当一个线程修改了 volatile 变量,新值会立刻同步到主内存,并且其他线程读取时会从主内存读取,保证了线程间的可见性。
  • 禁止指令重排volatile 关键字会插入内存屏障,禁止 JVM 和 CPU 对其前后的指令进行重排序优化。

适用场景:一个线程写,多个线程读的简单状态标志。

class Worker implements Runnable {
    // 使用 volatile 保证 flag 的可见性
    private volatile boolean flag = true;
    public void stop() {
        this.flag = false;
    }
    @Override
    public void run() {
        while (flag) {
            // do something
            System.out.println("Worker is running...");
        }
        System.out.println("Worker stopped.");
    }
}

第六章:高级并发工具

JUC (java.util.concurrent) 包是 Java 并发包的精髓,提供了大量强大的并发工具。

1 线程池 (Executor 框架)

频繁地创建和销毁线程非常消耗资源,线程池可以复用已创建的线程,提高性能。

核心接口和类:

  • Executor:顶层接口,只定义了 execute(Runnable) 方法。
  • ExecutorService:扩展了 Executor,添加了生命周期管理(shutdown(), submit() 等)。
  • ThreadPoolExecutor:最核心的线程池实现类。
  • Executors:一个工具类,提供了创建预定义配置线程池的静态工厂方法。

常用线程池:

  1. FixedThreadPool (固定大小线程池)
    ExecutorService executor = Executors.newFixedThreadPool(10);
  2. CachedThreadPool (缓存线程池)
    ExecutorService executor = Executors.newCachedThreadPool();
  3. SingleThreadExecutor (单线程执行器)
    ExecutorService executor = Executors.newSingleThreadExecutor();

最佳实践避免使用 Executors 创建线程池,因为它创建的线程池队列(如 LinkedBlockingQueue)是无界的,可能导致内存溢出,推荐直接使用 ThreadPoolExecutor 自定义参数。

// 推荐的自定义线程池创建方式
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    corePoolSize,      // 核心线程数
    maximumPoolSize,   // 最大线程数
    keepAliveTime,     // 空闲线程存活时间
    TimeUnit.SECONDS, // 时间单位
    new ArrayBlockingQueue<>(100), // 有界任务队列
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

2 CountDownLatch (倒计时门闩)

CountDownLatch 允许一个或多个线程等待其他一组线程完成操作。

场景:主线程等待所有子任务线程执行完毕后再继续。

import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        int workerCount = 3;
        CountDownLatch latch = new CountDownLatch(workerCount);
        for (int i = 0; i < workerCount; i++) {
            new Thread(() -> {
                System.out.println("Worker " + Thread.currentThread().getName() + " is working...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Worker " + Thread.currentThread().getName() + " finished.");
                latch.countDown(); // 完成任务,计数器减一
            }).start();
        }
        System.out.println("Main thread is waiting for workers to finish...");
        latch.await(); // 阻塞,直到计数器为 0
        System.out.println("All workers finished. Main thread continues.");
    }
}

3 CyclicBarrier (循环栅栏)

CyclicBarrier 让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,所有线程才会继续执行。CyclicBarrier 可以被循环使用。

场景:多个线程分阶段处理任务,每个阶段都需要所有线程都准备好后才能进入下一阶段。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        int parties = 3;
        CyclicBarrier barrier = new CyclicBarrier(parties, () -> {
            System.out.println("所有线程都已到达屏障,开始下一阶段!");
        });
        for (int i = 0; i < parties; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " 到达屏障 A");
                try {
                    barrier.await(); // 等待其他线程
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 到达屏障 B");
                try {
                    barrier.await(); // 再次等待
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 所有任务完成");
            }).start();
        }
    }
}

4 Semaphore (信号量)

Semaphore 用于控制同时访问特定资源的线程数量。

场景:停车场有 5 个车位,最多只能停 5 辆车。

import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
    public static void main(String[] args) {
        int permits = 3; // 模拟 3 个资源
        Semaphore semaphore = new Semaphore(permits);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 尝试获取资源...");
                    semaphore.acquire(); // 获取一个许可,如果已满则阻塞
                    System.out.println(Thread.currentThread().getName() + " 成功获取资源,剩余: " + semaphore.availablePermits());
                    Thread.sleep(2000); // 模拟使用资源
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // 释放一个许可
                    System.out.println(Thread.currentThread().getName() + " 释放资源,剩余: " + semaphore.availablePermits());
                }
            }).start();
        }
    }
}

5 BlockingQueue (阻塞队列)

BlockingQueue 是一个在队列基础上又支持了两个附加操作的队列:

  1. 支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满。
  2. 支持阻塞的移除方法:当队列空时,获取元素的线程会等待队列变为非空。

实现类ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue 等。

场景:生产者-消费者模型的最完美实现。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
class Producer implements Runnable {
    private final BlockingQueue<Integer> queue;
    public Producer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println("生产者生产: " + i);
                queue.put(i); // 如果队列满,这里会阻塞
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class Consumer implements Runnable {
    private final BlockingQueue<Integer> queue;
    public Consumer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        try {
            while (true) {
                Integer item = queue.take(); // 如果队列空,这里会阻塞
                System.out.println("消费者消费: " + item);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class BlockingQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
        new Thread(new Producer(queue)).start();
        new Thread(new Consumer(queue)).start();
    }
}

第七章:原子类与无锁编程

1 java.util.concurrent.atomic

Atomic 包下的类都利用了 CAS (Compare-And-Swap) 机制来实现原子操作,它们通常比 synchronized 性能更高,因为它们不涉及线程阻塞和上下文切换。

常用类:

  • AtomicInteger: 原子整数
  • AtomicLong: 原子长整型
  • AtomicBoolean: 原子布尔值
  • AtomicReference: 原子引用

示例: 使用 AtomicInteger 实现线程安全的计数器

import java.util.concurrent.atomic.AtomicInteger;
class AtomicCounter {
    private final AtomicInteger count = new AtomicInteger(0);
    public void increment() {
        // 内部使用 CAS 实现,是原子操作
        count.incrementAndGet();
    }
    public int getCount() {
        return count.get();
    }
}

2 CAS (Compare-And-Swap) 原理

CAS 是一条 CPU 并发原语,其思想是“我认为这个值应该是 V,如果是的话,就更新为新值 N,否则就不更新,并告诉我现在的值是多少”。

CAS 包含三个操作数:

  • V:要更新的变量
  • A:预期值
  • B:新值

当且仅当 V 的值等于 A 时,CAS 会通过原子方式将 V 的值更新为 B;否则,不会执行任何操作。

CAS 的优点是无锁,避免了线程阻塞,在高并发下性能更好,但它的缺点是:

  1. ABA 问题:如果一个值原来是 A,变成了 B,又变回了 A,CAS 操作会认为它没有变化,可以通过在变量上附加版本号(如 AtomicStampedReference)来解决。
  2. 自旋开销:CAS 失败,会不断重试,在竞争激烈时,重试开销可能很大。

第八章:Java 内存模型

1 JMM 是什么?

Java 内存模型 是一个抽象的概念,它定义了一套规则,用于规范在多线程环境下,哪些内存操作是可见的,以及如何进行指令重排序,它的目标是解决在多线程下,由于处理器缓存、指令重排序等导致的内存可见性问题。

2 可见性、有序性与原子性

  • 原子性:一个或多个操作,要么全部执行且执行的过程不会被任何因素打断,要么就都不执行。synchronizedLock 可以保证原子性。
  • 可见性:当一个线程修改了一个共享变量的值,其他线程能够立即得知这个修改。volatilesynchronizedfinal 可以保证可见性。
  • 有序性:即程序执行的顺序按照代码的先后顺序执行。volatilesynchronized 可以保证有序性。

3 happens-before 原则

happens-before 原则是判断内存可见性的重要依据,如果两个操作之间存在 happens-before 关系,那么前一个操作的结果对后一个操作就是可见的。

  1. 程序次序规则:在一个线程内,书写在前面的代码 happens-before 书写在后面的代码。
  2. 管程锁定规则:一个 unlock 操作 happens-before 后面对同一个锁的 lock 操作。
  3. volatile 变量规则:对一个 volatile 变量的写操作 happens-before 后面对这个变量的读操作。
  4. 线程启动规则:线程的 start() 方法 happens-before 于此线程的每一个动作。
  5. 线程终止规则:线程中的所有操作都 happens-before 对此线程的终止检测。
  6. 传递性:A happens-before B,且 B happens-before C,A happens-before C。

第九章:最佳实践与常见陷阱

1 最佳实践

  1. 优先使用并发工具类:尽量使用 JUC 包中的高级工具,而不是自己用 synchronized 从零开始实现。
  2. 避免过度同步:只在必要时进行同步,否则会降低并发性能。
  3. 优先使用 volatileAtomic:对于简单的状态标志或计数器,它们比锁更高效。
  4. 谨慎使用 synchronized:尽量使用 synchronized 代码块而不是同步整个方法,并指定精确的锁对象。
  5. 使用线程池:不要手动创建线程,使用 ThreadPoolExecutor
  6. 持有锁的时间尽可能短:在 synchronized 块内不要调用耗时操作(如 I/O)。
  7. 优先使用 BlockingQueue 实现生产者-消费者:它简洁、高效且不易出错。
  8. 文档化线程安全策略:明确说明类或方法的线程安全级别。

2 常见陷阱

  1. 死锁

    • 原因:两个或多个线程互相等待对方持有的锁,导致谁也无法继续执行。
    • 四个必要条件:互斥、占有且等待、不可剥夺、循环等待。
    • 预防:破坏循环等待条件(按固定顺序获取锁)、使用超时锁、避免嵌套锁。
  2. 活锁

    • 现象:线程虽然没有阻塞,但彼此之间互相谦让,导致谁也无法继续执行。
    • 示例:两个人在过道相遇,都往左边让,结果又堵住了;然后又都往右边让,还是堵住。
    • 解决:引入随机性,或者设置一个“不谦让”的规则。
  3. 饥饿

    • 现象:某个线程因为无法获取到所需的资源,导致一直无法执行。
    • 原因:通常是因为锁的实现是“不公平”的,某些线程可能一直获取不到锁。
    • 解决:使用公平锁(但会降低吞吐量)。

总结与学习路径

Java 多线程是一个庞大而复杂的领域,但也是成为一名优秀 Java 工程师的必备技能。

学习路径建议:

  1. 入门阶段:掌握 Thread, Runnable, synchronized 的基本用法,理解线程状态和生命周期,能够写出简单的多线程程序。
  2. 进阶阶段:深入学习 Lock, volatile, wait/notify,理解线程间的通信和协作,开始使用 Executor 框架和 JUC 包中的工具类,如 CountDownLatch, BlockingQueue 等。
  3. 精通阶段:深入理解 Java 内存模型,掌握 happens-before 原则,学习 CAS 原理和 Atomic 类,理解无锁编程,能够分析并解决死锁、活锁等复杂问题,并能根据业务场景设计出高效、健壮的并发方案。
  4. 实践阶段:在实际项目中大量运用并发知识,阅读优秀开源框架的源码(如 Netty, Disruptor),学习大师们的并发设计思想。

希望这份教程能为你开启 Java 多世界的大门,理论结合实践是掌握任何技术的最佳途径,祝你学习顺利!