Java 多线程学习手册:从入门到实习实战
第一章 Java 多线程基础
1.1 进程与线程的概念
- 进程:操作系统分配资源的最小单位,每个进程有独立的内存空间。
- 线程:CPU 调度的最小单位,是进程中的执行流,多个线程共享进程的堆和方法区,但有独立的程序计数器、虚拟机栈和本地方法栈。
- 关系:一个进程可以包含多个线程,线程是进程的“轻量级”执行单元。
1.2 Java 中创建线程的三种方式
方式一:继承 Thread 类
// 自定义线程类继承 Thread
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行:" + Thread.currentThread().getName());
}
}
// 使用
public class ThreadDemo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // 启动线程(注意:不能直接调用 run())
}
}方式二:实现 Runnable 接口(推荐)
// 实现 Runnable 接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程执行:" + Thread.currentThread().getName());
}
}
// 使用
public class RunnableDemo {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread t2 = new Thread(runnable);
t2.start();
// Lambda 表达式简化(Java 8+)
new Thread(() -> System.out.println("Lambda 线程")).start();
}
}方式三:使用 Callable 和 Future(带返回值)
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
// 实现 Callable 接口
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("线程执行:" + Thread.currentThread().getName());
return 42; // 带返回值
}
}
// 使用
public class CallableDemo {
public static void main(String[] args) throws Exception {
MyCallable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
Integer result = futureTask.get(); // 阻塞等待线程执行完成并获取结果
System.out.println("线程返回结果:" + result);
}
}1.3 线程的生命周期
Java 线程有 5 种状态,通过 Thread.getState() 可获取:
- NEW(新建):线程对象刚创建,未调用
start()。 - RUNNABLE(就绪/运行):线程已调用
start(),等待 CPU 调度或正在执行。 - BLOCKED(阻塞):线程等待锁资源(如进入
synchronized块失败)。 - WAITING(无限等待):线程调用
wait()、join()等方法,需其他线程唤醒。 - TIMED_WAITING(计时等待):线程调用
sleep(long)、wait(long)等方法,超时后自动恢复。 - TERMINATED(终止):线程执行完成或异常退出。
1.4 练习题
- 用
Thread和Runnable分别创建 3 个线程,每个线程打印 1-5 的数字。 - 用
Callable创建线程,计算 1-100 的和并返回结果。 - 观察线程状态:创建一个线程,分别在
start()前、start()后、sleep()时、执行完后打印其状态。
第二章 线程的基本控制
2.1 start() 与 run() 的区别
start():启动线程,让线程进入 RUNNABLE 状态,由 JVM 调用run()方法(多线程执行)。run():只是普通方法调用,不会启动新线程(单线程执行)。
2.2 线程休眠:sleep(long millis)
- 作用:让当前线程暂停指定毫秒数,进入 TIMED_WAITING 状态,不释放锁。
- 示例:
public class SleepDemo {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
System.out.println(i);
Thread.sleep(1000); // 休眠 1 秒
}
}
}2.3 线程等待与通知:wait()、notify()、notifyAll()
- 这些方法必须在 synchronized 块/方法中调用(需持有锁)。
wait():让当前线程释放锁并进入 WAITING 状态,等待其他线程唤醒。notify():随机唤醒一个正在等待的线程。notifyAll():唤醒所有正在等待的线程。
示例:生产者-消费者模型(简单版)
class SharedResource {
private int data;
private boolean hasData = false;
// 生产者方法
public synchronized void produce(int value) throws InterruptedException {
while (hasData) { // 用 while 防止虚假唤醒
wait(); // 等待消费者消费
}
data = value;
hasData = true;
System.out.println("生产:" + data);
notify(); // 唤醒消费者
}
// 消费者方法
public synchronized void consume() throws InterruptedException {
while (!hasData) {
wait(); // 等待生产者生产
}
System.out.println("消费:" + data);
hasData = false;
notify(); // 唤醒生产者
}
}
// 使用
public class WaitNotifyDemo {
public static void main(String[] args) {
SharedResource resource = new SharedResource();
new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) resource.produce(i);
} catch (InterruptedException e) { e.printStackTrace(); }
}, "生产者").start();
new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) resource.consume();
} catch (InterruptedException e) { e.printStackTrace(); }
}, "消费者").start();
}
}2.4 线程加入:join()
- 作用:让当前线程等待调用
join()的线程执行完毕后再继续执行。 - 示例:
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("子线程执行:" + i);
try { Thread.sleep(500); } catch (InterruptedException e) {}
}
});
t.start();
t.join(); // 主线程等待子线程执行完
System.out.println("主线程执行完毕");
}
}2.5 线程中断:interrupt()
- 作用:给线程发送中断信号,但不会直接停止线程,需线程自身响应中断。
- 相关方法:
interrupt():设置中断标志位为true。isInterrupted():检查中断标志位。interrupted():检查并清除中断标志位(静态方法)。
- 示例:
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("线程运行中...");
try {
Thread.sleep(500); // sleep 会响应中断并抛出异常,清除标志位
} catch (InterruptedException e) {
System.out.println("线程被中断,准备退出");
Thread.currentThread().interrupt(); // 重新设置标志位,让循环退出
}
}
});
t.start();
Thread.sleep(2000);
t.interrupt(); // 发送中断信号
}
}2.6 线程让步:yield()
- 作用:让当前线程放弃 CPU 时间片,回到 RUNNABLE 状态,让其他线程执行(仅作提示,不保证一定生效)。
2.7 练习题
- 用
wait()和notify()实现两个线程交替打印 1-100(线程 1 打印奇数,线程 2 打印偶数)。 - 主线程创建 3 个子线程,分别打印 A、B、C,要求主线程等待所有子线程执行完后再打印 "Done"(用
join())。 - 设计一个线程,在循环中打印数字,当主线程调用
interrupt()后,线程安全退出。
第三章 线程安全与同步机制
3.1 线程安全问题的产生
当多个线程同时访问共享可变资源时,可能出现数据不一致。
示例:非线程安全的计数器
class Counter {
private int count = 0;
public void increment() { count++; } // 非原子操作
public int getCount() { return count; }
}
public class UnsafeDemo {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> { for (int i = 0; i < 10000; i++) counter.increment(); });
Thread t2 = new Thread(() -> { for (int i = 0; i < 10000; i++) counter.increment(); });
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("最终计数:" + counter.getCount()); // 结果可能小于 20000
}
}3.2 synchronized 关键字
- 作用:保证同一时间只有一个线程执行指定代码块/方法,保证原子性、可见性、有序性。
- 使用方式:
- 同步实例方法:锁对象是
this。 - 同步静态方法:锁对象是类的
Class对象。 - 同步代码块:锁对象是自定义对象(推荐,粒度更细)。
- 同步实例方法:锁对象是
示例:用 synchronized 修复计数器
class SafeCounter {
private int count = 0;
// 同步方法
public synchronized void increment() { count++; }
// 或同步代码块
public void increment2() {
synchronized (this) { count++; }
}
public int getCount() { return count; }
}3.3 volatile 关键字
- 作用:保证变量的可见性(一个线程修改变量,其他线程立即可见)和禁止指令重排序,但不保证原子性。
- 适用场景:状态标记(如
boolean flag = true;)。
示例:volatile 状态标记
class VolatileDemo {
private volatile boolean flag = true;
public void run() {
while (flag) { /* 执行任务 */ }
System.out.println("线程停止");
}
public void stop() { flag = false; }
}3.4 原子类(java.util.concurrent.atomic)
- 提供原子操作的类,如
AtomicInteger、AtomicLong、AtomicBoolean等,底层基于 CAS(Compare-And-Swap)实现。
示例:AtomicInteger 计数器
import java.util.concurrent.atomic.AtomicInteger;
class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() { count.incrementAndGet(); } // 原子递增
public int getCount() { return count.get(); }
}3.5 Lock 接口与 ReentrantLock
Lock是更灵活的锁机制,需手动加锁和解锁,支持公平锁、非公平锁、可中断锁等。- 常用实现:
ReentrantLock(可重入锁)。
示例:ReentrantLock 修复计数器
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class LockCounter {
private int count = 0;
private Lock lock = new ReentrantLock(); // 默认非公平锁
public void increment() {
lock.lock(); // 加锁
try {
count++;
} finally {
lock.unlock(); // 必须在 finally 中解锁,防止死锁
}
}
public int getCount() { return count; }
}3.6 死锁
- 定义:两个或多个线程互相等待对方释放锁,导致永久阻塞。
- 产生条件:
- 互斥条件:锁只能被一个线程持有。
- 请求与保持条件:线程持有锁的同时请求新锁。
- 不剥夺条件:锁不能被强制剥夺,只能主动释放。
- 循环等待条件:线程形成循环等待链。
- 避免方法:破坏上述任一条件(如按固定顺序获取锁)。
示例:死锁演示
class DeadLockDemo {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock1) {
System.out.println("线程1持有lock1,等待lock2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) { System.out.println("线程1持有lock1和lock2"); }
}
}).start();
new Thread(() -> {
synchronized (lock2) {
System.out.println("线程2持有lock2,等待lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) { System.out.println("线程2持有lock2和lock1"); }
}
}).start();
}
}3.7 练习题
- 用
synchronized实现线程安全的单例模式(双重检查锁定)。 - 用
ReentrantLock实现生产者-消费者模型。 - 设计一个程序,演示死锁,并修改代码避免死锁(如按固定顺序获取锁)。
- 用
AtomicInteger实现一个线程安全的计数器,并测试 10 个线程各递增 10000 次的结果。
第四章 线程池
4.1 线程池的概念与优势
- 线程池:预先创建若干线程,放入池中,需要时直接取用,用完放回,避免频繁创建和销毁线程的开销。
- 优势:降低资源消耗、提高响应速度、便于线程管理。
4.2 ThreadPoolExecutor 核心参数
Java 线程池的核心实现是 ThreadPoolExecutor,构造参数如下:
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数(即使空闲也保留)
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程空闲存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂(创建线程)
RejectedExecutionHandler handler // 拒绝策略(任务满时的处理)
)任务执行流程:
- 线程数 < 核心线程数 → 创建核心线程执行任务。
- 线程数 ≥ 核心线程数 → 任务加入队列。
- 队列满 → 创建非核心线程执行任务。
- 线程数 = 最大线程数且队列满 → 执行拒绝策略。
常见拒绝策略:
AbortPolicy:默认,抛出异常。CallerRunsPolicy:由调用线程执行任务。DiscardPolicy:直接丢弃任务。DiscardOldestPolicy:丢弃队列中最老的任务,执行当前任务。
4.3 常见的线程池类型(Executors 工具类)
1. FixedThreadPool(固定大小线程池)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 核心线程数 = 最大线程数,队列无界
ExecutorService fixedPool = Executors.newFixedThreadPool(3);2. CachedThreadPool(可缓存线程池)
// 核心线程数=0,最大线程数=Integer.MAX_VALUE,线程空闲 60 秒回收
ExecutorService cachedPool = Executors.newCachedThreadPool();3. SingleThreadExecutor(单线程线程池)
// 核心线程数=最大线程数=1,保证任务按顺序执行
ExecutorService singlePool = Executors.newSingleThreadExecutor();4. ScheduledThreadPool(定时任务线程池)
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
// 支持定时和周期性任务
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2);
// 延迟 1 秒执行
scheduledPool.schedule(() -> System.out.println("延迟任务"), 1, TimeUnit.SECONDS);
// 延迟 1 秒后,每 2 秒执行一次
scheduledPool.scheduleAtFixedRate(() -> System.out.println("周期任务"), 1, 2, TimeUnit.SECONDS);4.4 线程池的合理配置
- CPU 密集型任务:核心线程数 = CPU 核心数 + 1(如
Runtime.getRuntime().availableProcessors() + 1)。 - I/O 密集型任务:核心线程数 = 2 * CPU 核心数(或根据实际压测调整)。
示例:自定义线程池(推荐,避免 OOM)
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 自定义线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60L, TimeUnit.SECONDS, // 空闲时间
new LinkedBlockingQueue<>(10), // 有界队列(容量 10)
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 提交任务
for (int i = 0; i < 20; i++) {
final int taskId = i;
pool.execute(() -> {
System.out.println("线程 " + Thread.currentThread().getName() + " 执行任务 " + taskId);
try { Thread.sleep(1000); } catch (InterruptedException e) {}
});
}
pool.shutdown(); // 关闭线程池(等待任务执行完)
}
}4.5 练习题
- 用
FixedThreadPool提交 10 个任务,每个任务打印当前线程名和任务编号。 - 自定义一个线程池,核心线程数 3,最大线程数 6,队列容量 5,拒绝策略为
CallerRunsPolicy,提交 20 个任务观察执行情况。 - 用
ScheduledThreadPool实现一个定时任务,每隔 3 秒打印一次当前时间。 - 设计一个程序,对比
FixedThreadPool和CachedThreadPool执行 100 个短任务的效率。
第五章 并发工具类
5.1 CountDownLatch(倒计时门闩)
- 作用:让一个或多个线程等待其他线程完成后再执行。
- 核心方法:
CountDownLatch(int count):构造函数,count 为等待的线程数。countDown():count 减 1。await():等待 count 变为 0。
示例:主线程等待 3 个子线程执行完
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
final int id = i;
new Thread(() -> {
System.out.println("线程 " + id + " 执行完毕");
latch.countDown(); // 计数减 1
}).start();
}
latch.await(); // 主线程等待
System.out.println("所有线程执行完毕,主线程继续");
}
}5.2 CyclicBarrier(循环屏障)
- 作用:让多个线程互相等待,到达屏障点后一起执行。
- 核心方法:
CyclicBarrier(int parties):parties 为等待的线程数。await():线程到达屏障点,等待其他线程。
示例:3 个线程到达屏障点后一起执行
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("所有线程到达屏障点,开始执行后续任务"));
for (int i = 0; i < 3; i++) {
final int id = i;
new Thread(() -> {
System.out.println("线程 " + id + " 到达屏障点");
try {
barrier.await(); // 等待其他线程
System.out.println("线程 " + id + " 继续执行");
} catch (Exception e) { e.printStackTrace(); }
}).start();
}
}
}5.3 Semaphore(信号量)
- 作用:控制同时访问资源的线程数量(限流)。
- 核心方法:
Semaphore(int permits):permits 为许可数。acquire():获取许可(无许可则阻塞)。release():释放许可。
示例:限制最多 3 个线程同时执行
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
final int id = i;
new Thread(() -> {
try {
semaphore.acquire(); // 获取许可
System.out.println("线程 " + id + " 获得许可,开始执行");
Thread.sleep(1000);
System.out.println("线程 " + id + " 执行完毕,释放许可");
semaphore.release(); // 释放许可
} catch (InterruptedException e) { e.printStackTrace(); }
}).start();
}
}
}5.4 Exchanger(交换器)
- 作用:两个线程在同步点交换数据。
- 核心方法:
exchange(V x):交换数据,阻塞等待另一个线程。
示例:两个线程交换字符串
import java.util.concurrent.Exchanger;
public class ExchangerDemo {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
new Thread(() -> {
try {
String data1 = "线程1的数据";
System.out.println("线程1准备交换:" + data1);
String received = exchanger.exchange(data1);
System.out.println("线程1收到:" + received);
} catch (InterruptedException e) { e.printStackTrace(); }
}).start();
new Thread(() -> {
try {
String data2 = "线程2的数据";
System.out.println("线程2准备交换:" + data2);
Thread.sleep(1000); // 模拟延迟
String received = exchanger.exchange(data2);
System.out.println("线程2收到:" + received);
} catch (InterruptedException e) { e.printStackTrace(); }
}).start();
}
}5.5 练习题
- 用
CountDownLatch模拟 100 米赛跑:5 个运动员线程,主线程等待所有运动员到达终点后宣布比赛结束。 - 用
CyclicBarrier模拟 3 个玩家加载游戏,全部加载完成后一起进入游戏。 - 用
Semaphore实现一个数据库连接池,限制最多 5 个连接。 - 用
Exchanger实现两个线程交换整数数组。
第六章 高级并发主题
6.1 ThreadLocal
- 作用:为每个线程提供独立的变量副本,线程间互不影响。
- 核心方法:
set(T value):设置当前线程的变量值。get():获取当前线程的变量值。remove():移除当前线程的变量值(防止内存泄漏)。
示例:ThreadLocal 存储用户信息
class UserContext {
private static ThreadLocal<String> userHolder = new ThreadLocal<>();
public static void setUser(String user) { userHolder.set(user); }
public static String getUser() { return userHolder.get(); }
public static void clear() { userHolder.remove(); } // 必须清理
}
public class ThreadLocalDemo {
public static void main(String[] args) {
new Thread(() -> {
UserContext.setUser("用户A");
System.out.println("线程1:" + UserContext.getUser());
UserContext.clear();
}).start();
new Thread(() -> {
UserContext.setUser("用户B");
System.out.println("线程2:" + UserContext.getUser());
UserContext.clear();
}).start();
}
}6.2 并发集合
ConcurrentHashMap:线程安全的 HashMap(替代Hashtable)。CopyOnWriteArrayList:线程安全的 ArrayList(读多写少场景)。CopyOnWriteArraySet:线程安全的 Set。ConcurrentLinkedQueue:线程安全的无界队列。
示例:ConcurrentHashMap 使用
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("A", 1);
map.put("B", 2);
System.out.println(map.get("A"));
}
}6.3 Future 与 FutureTask
Future:表示异步计算的结果,可检查计算是否完成、获取结果。FutureTask:Future的实现类,可包装Callable或Runnable。
示例:Future 获取异步任务结果
import java.util.concurrent.*;
public class FutureDemo {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(1);
Future<Integer> future = pool.submit(() -> {
Thread.sleep(1000);
return 42;
});
System.out.println("等待结果...");
Integer result = future.get(); // 阻塞等待
System.out.println("结果:" + result);
pool.shutdown();
}
}6.4 CompletableFuture 异步编程
- Java 8 引入,支持链式调用、组合多个异步任务、异常处理等。
示例:CompletableFuture 链式调用
import java.util.concurrent.CompletableFuture;
public class CompletableFutureDemo {
public static void main(String[] args) {
// 异步任务1:计算 1+2
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("任务1执行");
return 1 + 2;
});
// 任务2:接收任务1结果,乘以 3
CompletableFuture<Integer> future2 = future1.thenApplyAsync(result -> {
System.out.println("任务2执行,输入:" + result);
return result * 3;
});
// 任务3:接收任务2结果,打印
future2.thenAccept(result -> System.out.println("最终结果:" + result));
// 等待所有任务完成
CompletableFuture.allOf(future1, future2).join();
}
}6.5 练习题
- 用
ThreadLocal实现一个简单的事务上下文,存储当前线程的事务 ID。 - 用
ConcurrentHashMap统计 10 个线程并发写入 10000 个键值对后的大小。 - 用
CompletableFuture实现:异步查询用户信息,再异步查询用户订单,最后合并结果打印。 - 设计一个程序,对比
HashMap和ConcurrentHashMap在多线程并发写入时的安全性。
实习实战建议
- 重点掌握:线程池配置、
synchronized/Lock线程安全、CompletableFuture异步编程、并发集合。 - 实践项目:
- 实现一个简单的异步任务处理系统(用线程池 +
CompletableFuture)。 - 设计一个高并发的秒杀系统(用
Semaphore限流、ConcurrentHashMap存储库存)。
- 实现一个简单的异步任务处理系统(用线程池 +
- 面试准备:
- 线程池参数、执行流程、拒绝策略。
synchronized和Lock的区别。volatile的作用和原理。ConcurrentHashMap的底层实现(JDK 1.8 版本)。
需要我帮你详细解析某个章节的练习题答案,或者针对某个实战场景提供代码示例吗?
答疑解惑
我帮你把这段学习过程中的关键问题,整理成一套结构清晰、可复习、可用于面试表达的问题清单👇
🧠 一、核心问题(你最初的困惑)
- 为什么使用
wait()/notify()的代码不能实现线程交替执行? - 线程之间如果没有共享变量,是否还能进行通信?
- 仅靠参数(如
n)判断条件,为什么会导致线程无法协作甚至死锁?
🔄 二、机制理解类问题(wait / notify 本质)
wait()和notify()在 Java 中的本质是什么?- 调用
wait()时,线程具体发生了什么?(锁、状态、执行位置) - 调用
notify()后,被唤醒的线程会立即执行吗? notify()和notifyAll()的区别是什么?什么时候必须用notifyAll()?- 为什么
wait()/notify()必须写在synchronized代码块中?
🔁 三、执行流程类问题(非常关键)
- 线程从
wait()被唤醒后,是从头执行,还是从wait()之后继续执行? - 被
notify()唤醒的线程,为什么还需要重新竞争锁? - 一个完整的 wait → notify → 执行 的线程调度流程是怎样的?
⚠️ 四、常见错误与陷阱(你已经踩到的坑)
- 为什么必须用
while而不是if来包裹wait()? - 什么是“虚假唤醒”(spurious wakeup)?
- 为什么没有共享状态变量会导致线程无法正确协作?
- 为什么
notify()可能导致线程“卡死”或“饿死”?
🧩 五、设计思想类问题(本质提升)
- 线程交替执行的本质是什么?(不是奇偶判断,而是执行顺序控制)
- 线程通信的三要素是什么?
- 共享变量
- 条件判断
- 同步机制
🚀 六、扩展与进阶问题(更现代的写法)
wait/notify是否是唯一的线程通信方式?ReentrantLock + Condition和wait/notify的区别是什么?- 什么时候应该使用
BlockingQueue替代手写线程同步? Semaphore、CountDownLatch、CyclicBarrier分别适用于什么场景?
🎯 最终总结(一句话版本)
你这一整串问题,本质可以归纳为一句话:
👉 Java 线程如何基于共享状态,实现“可控的执行顺序”?
而你已经在逐步掌握:
- ✔ 线程通信(wait/notify)
- ✔ 执行调度机制
- ✔ 并发设计思想
📌 如果你想再进一层(强烈建议)
可以把这些问题升级成面试级表达:
👉 “请你设计一个多线程交替执行的方案,并分析不同实现方式的优劣”
我可以帮你把这一整套整理成面试标准答案 + 模板代码 + 思路图,让你这一块直接超过大多数人 👍
我帮你把刚才的问题整理成更清晰、结构化的版本(适合笔记/面试表达):
🎯 问题整理:Java 多线程中 join() 的行为
在 Java 多线程编程中,当主线程通过 join() 等待多个子线程时,我有以下几个疑问:
1️⃣ 子线程执行顺序问题
- 当存在多个子线程时:
- 子线程的执行顺序是否会按照
join()的调用顺序来执行? - 还是说子线程的执行顺序是完全不确定的?
- 子线程的执行顺序是否会按照
2️⃣ 主线程阻塞机制
当主线程依次调用多个
join():javat1.join(); t2.join(); t3.join();- 主线程是否会:
- 先阻塞在
t1.join()? - 等 t1 执行完后,再执行
t2.join()?
- 先阻塞在
- 是否可以理解为:主线程是“按 join 顺序逐个等待线程”?
- 主线程是否会:
3️⃣ join 顺序与执行结果的关系
如果:
javat1.start(); t2.start(); t2.join(); t1.join();- 是否意味着:
- t2 一定比 t1 先执行完?
- 还是说:
join()只影响主线程等待顺序,不影响子线程执行顺序?
- 是否意味着:
4️⃣ 线程执行控制能力
join()是否可以用于控制线程执行顺序?如果想让线程严格按顺序执行(如 t1 → t2 → t3),是否必须:
javat1.start(); t1.join(); t2.start(); t2.join(); t3.start(); t3.join();
🧠 核心疑问总结(一句话版)
在 Java 多线程中,
join()到底是:
- 控制“线程执行顺序”
- 还是仅仅控制“主线程等待顺序”?