本文系统讲解Java多线程编程,从线程基础到并发工具类,覆盖线程创建、线程安全、锁机制、线程池、并发容器、原子类、CompletableFuture等全链路知识,配合大量实战代码,帮助你全面掌握Java并发编程。


目录

  1. 什么是多线程

  2. 线程的创建方式

  3. 线程的生命周期

  4. 线程的常用方法

  5. 线程安全问题

  6. synchronized关键字

  7. volatile关键字

  8. ReentrantLock

  9. 线程通信

  10. 线程池

  11. 并发容器

  12. 原子类

  13. CountDownLatch与CyclicBarrier

  14. CompletableFuture异步编程

  15. ThreadLocal

  16. 实战:多线程下载文件

  17. 常见面试题精选

  18. 总结


1. 什么是多线程

1.1 进程与线程

进程(Process):
  操作系统分配资源的最小单位
  一个进程就是一个运行中的程序
  例如:一个正在运行的IDEA就是一个进程
​
线程(Thread):
  CPU调度的最小单位
  一个进程可以包含多个线程
  例如:IDEA进程中,一个线程负责代码编辑,一个线程负责代码编译,一个线程负责代码提示
​
关系:
  进程是线程的容器
  一个进程至少有一个线程(主线程)
  同一进程内的线程共享该进程的资源(内存、文件句柄等)
┌─────────────────────────────────────────────┐
│                  进程 (JVM)                   │
│                                              │
│   ┌──────────┐  ┌──────────┐  ┌──────────┐  │
│   │  线程1    │  │  线程2    │  │  线程3    │  │
│   │  main()   │  │  处理请求  │  │  定时任务  │  │
│   └──────────┘  └──────────┘  └──────────┘  │
│                                              │
│   共享区域:堆内存、方法区、静态变量           │
│   独立区域:栈内存、程序计数器                 │
└─────────────────────────────────────────────┘

1.2 为什么需要多线程

场景1:提高CPU利用率
  单线程:CPU等待IO时处于空闲状态
  多线程:一个线程等待IO时,CPU可以执行其他线程
​
场景2:提高响应速度
  Web服务器:一个请求一个线程,多个请求并发处理
  不用多线程:请求排队处理,用户体验极差
​
场景3:充分利用多核CPU
  单线程只能使用一个CPU核心
  多线程可以同时使用多个CPU核心

1.3 并行与并发

并发(Concurrency):
  多个任务交替执行(单核CPU上的"同时")
  实际上是快速切换,宏观上看起来同时执行
​
并行(Parallelism):
  多个任务真正同时执行(多核CPU)
  每个核心执行一个任务
​
  并发:一个人交替做两件事
  并行:两个人同时做两件事

2. 线程的创建方式

2.1 方式一:继承Thread类

/**
 * 方式1:继承Thread类
 * 重写run()方法
 */
public class MyThread extends Thread {
​
    @Override
    public void run() {
        // 线程执行的代码
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - " + i);
            try {
                Thread.sleep(500);  // 休眠500ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
​
    public static void main(String[] args) {
        // 创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
​
        // 设置线程名
        t1.setName("线程A");
        t2.setName("线程B");
​
        // 启动线程(不是调用run(),而是调用start())
        t1.start();
        t2.start();
​
        // 注意:
        // t.start()  → 启动新线程,run()在新线程中执行
        // t.run()    → 只是普通方法调用,不会启动新线程
    }
}

2.2 方式二:实现Runnable接口

/**
 * 方式2:实现Runnable接口
 * 推荐使用,因为Java只支持单继承,实现接口更灵活
 */
public class MyRunnable implements Runnable {
​
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - " + i);
        }
    }
​
    public static void main(String[] args) {
        // 创建Runnable对象
        MyRunnable task = new MyRunnable();
​
        // 将Runnable传给Thread
        Thread t1 = new Thread(task, "线程A");
        Thread t2 = new Thread(task, "线程B");
​
        t1.start();
        t2.start();
    }
}

2.3 方式三:实现Callable接口(有返回值)

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
​
/**
 * 方式3:实现Callable接口
 * 可以有返回值,可以抛出异常
 */
public class MyCallable implements Callable<Integer> {
​
    @Override
    public Integer call() throws Exception {
        // 可以有返回值
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
​
    public static void main(String[] args) throws Exception {
        // 创建Callable对象
        MyCallable callable = new MyCallable();
​
        // 用FutureTask包装Callable
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
​
        // 创建线程
        Thread thread = new Thread(futureTask, "计算线程");
        thread.start();
​
        // 获取返回值(会阻塞当前线程直到计算完成)
        Integer result = futureTask.get();
        System.out.println("计算结果: " + result);  // 5050
    }
}

2.4 方式四:线程池创建(最常用)

import java.util.concurrent.*;
​
/**
 * 方式4:使用线程池
 * 生产环境最推荐的方式,避免频繁创建销毁线程
 */
public class ThreadPoolDemo {
​
    public static void main(String[] args) throws Exception {
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);
​
        // 提交Runnable任务
        executor.execute(() -> {
            System.out.println(Thread.currentThread().getName() + " 执行Runnable任务");
        });
​
        // 提交Callable任务
        Future<String> future = executor.submit(() -> {
            return Thread.currentThread().getName() + " 执行Callable任务";
        });
        System.out.println(future.get());
​
        // 关闭线程池
        executor.shutdown();
    }
}

2.5 四种方式对比

方式 返回值 异常处理 灵活性 使用场景
继承Thread 不能抛受检异常 低(单继承) 简单场景
实现Runnable 不能抛受检异常 无返回值的场景
实现Callable 可以抛受检异常 需要返回值的场景
线程池 有/无 可以 最高 生产环境首选

3. 线程的生命周期

Java线程有6种状态,定义在 Thread.State 枚举中。

                    start()
  NEW ──────────────────────────────→ RUNNABLE
  (新建)                                (可运行)
                                         │
                           ┌─────────────┼─────────────┐
                           │             │             │
                           ▼             ▼             ▼
                      BLOCKED       WAITING       TIMED_WAITING
                      (阻塞)        (无限等待)      (超时等待)
                       等锁          wait()        sleep(ms)
                       synchronized  join()        wait(ms)
                                    LockSupport    join(ms)
                                    .park()
                           │             │             │
                           └─────────────┼─────────────┘
                                         │
                                         ▼
                                    RUNNABLE
                                         │
                                         ▼
                                    TERMINATED
                                     (终止)

各状态详解

public class ThreadStateDemo {
​
    public static void main(String[] args) throws Exception {
​
        // 1. NEW:线程被创建但尚未启动
        Thread thread = new Thread(() -> {
            System.out.println("线程运行中");
        });
        System.out.println("创建后: " + thread.getState());  // NEW
​
        // 2. RUNNABLE:线程正在运行或等待CPU调度
        thread.start();
        System.out.println("启动后: " + thread.getState());  // RUNNABLE
​
        // 3. TIMED_WAITING:线程在有限时间内等待
        Thread.sleep(1000);  // 当前线程进入TIMED_WAITING
​
        // 4. WAITING:线程无限期等待
        Object lock = new Object();
        synchronized (lock) {
            lock.wait();  // 进入WAITING,等待notify()
        }
​
        // 5. BLOCKED:线程等待获取监视器锁
        // 等待进入synchronized块时进入BLOCKED
​
        // 6. TERMINATED:线程执行完毕
        thread.join();  // 等待thread执行完毕
        System.out.println("结束后: " + thread.getState());  // TERMINATED
    }
}

4. 线程的常用方法

4.1 基础方法

public class ThreadMethodsDemo {
​
    public static void main(String[] args) throws Exception {
​
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }, "工作线程");
​
        // start() - 启动线程
        thread.start();
​
        // getName() / setName() - 获取/设置线程名
        System.out.println(thread.getName());  // 工作线程
​
        // isAlive() - 判断线程是否存活
        System.out.println(thread.isAlive());  // true
​
        // join() - 等待该线程执行完毕
        thread.join();  // 当前线程阻塞,直到thread执行完毕
​
        // join(millis) - 最多等待指定毫秒数
        // thread.join(5000);  // 最多等5秒
​
        // Thread.sleep(millis) - 当前线程休眠
        Thread.sleep(1000);  // 休眠1秒
​
        // Thread.yield() - 让出CPU时间片
        // 提示调度器当前线程愿意让出CPU,但调度器可以忽略这个提示
        Thread.yield();
​
        // interrupt() - 中断线程
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                // 收到中断信号
                System.out.println("被中断了");
            }
        });
        t.start();
        t.interrupt();  // 发送中断信号
​
        // setDaemon(true) - 设置为守护线程
        // 守护线程在所有非守护线程结束后自动退出
        // 例如:GC线程就是守护线程
        Thread daemon = new Thread(() -> {
            while (true) {
                // 后台守护任务
            }
        });
        daemon.setDaemon(true);
        daemon.start();
​
        // setPriority() - 设置优先级(1-10)
        // 优先级只是建议,不保证执行顺序
        thread.setPriority(Thread.MAX_PRIORITY);  // 10
    }
}

4.2 wait/notify(线程间通信)

/**
 * wait/notify 实现生产者-消费者模式
 */
public class ProducerConsumerDemo {
​
    private static final Queue<Integer> queue = new LinkedList<>();
    private static final int MAX_SIZE = 5;
​
    public static void main(String[] args) {
        // 生产者
        Thread producer = new Thread(() -> {
            int value = 0;
            while (true) {
                synchronized (queue) {
                    while (queue.size() >= MAX_SIZE) {
                        try {
                            System.out.println("队列满了,生产者等待...");
                            queue.wait();  // 释放锁,进入等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    queue.offer(value);
                    System.out.println("生产: " + value + ",队列大小: " + queue.size());
                    value++;
                    queue.notifyAll();  // 通知消费者
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
​
        // 消费者
        Thread consumer = new Thread(() -> {
            while (true) {
                synchronized (queue) {
                    while (queue.isEmpty()) {
                        try {
                            System.out.println("队列空了,消费者等待...");
                            queue.wait();  // 释放锁,进入等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    int value = queue.poll();
                    System.out.println("消费: " + value + ",队列大小: " + queue.size());
                    queue.notifyAll();  // 通知生产者
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
​
        producer.start();
        consumer.start();
    }
}

wait/notify注意事项:

1. 必须在synchronized块中调用(必须持有锁)
2. wait()会释放锁,sleep()不会释放锁
3. notify()随机唤醒一个等待线程,notifyAll()唤醒所有等待线程
4. 被唤醒后需要重新获取锁才能继续执行
5. 使用while循环检查条件(不用if),防止虚假唤醒

5. 线程安全问题

5.1 什么是线程安全

/**
 * 线程安全问题演示
 * 多个线程同时修改共享变量,导致结果不正确
 */
public class ThreadUnsafeDemo {
​
    private static int count = 0;
​
    public static void main(String[] args) throws Exception {
        // 创建10个线程,每个线程对count加10000次
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    count++;  // 非原子操作!
                }
            });
            threads[i].start();
        }
​
        // 等待所有线程执行完毕
        for (Thread t : threads) {
            t.join();
        }
​
        // 期望结果:100000
        // 实际结果:可能是 87432、91256 等不确定的值
        System.out.println("count = " + count);
    }
}

5.2 为什么count++不是原子操作

count++ 实际上包含三个步骤:
​
  1. 读取:从主内存读取count的值到工作内存
  2. 修改:在工作内存中对count加1
  3. 写入:将修改后的值写回主内存
​
线程安全问题的根源:
​
  线程A:读取count = 0
  线程B:读取count = 0        ← 线程A还没写回,B就读了
  线程A:count = 0 + 1 = 1,写回count = 1
  线程B:count = 0 + 1 = 1,写回count = 1
​
  结果:加了两次,count只增加了1

5.3 线程安全问题的三大根源

1. 原子性(Atomicity)
   操作被中断,其他线程看到中间状态
   例如:count++ 被其他线程打断
​
2. 可见性(Visibility)
   一个线程修改了共享变量,其他线程看不到最新值
   原因:CPU缓存、指令重排序
​
3. 有序性(Ordering)
   代码执行顺序与编写顺序不一致
   原因:编译器优化、CPU指令重排序

6. synchronized关键字

synchronized是Java内置的互斥锁,保证同一时刻只有一个线程执行临界区代码。

6.1 三种用法

public class SynchronizedDemo {
​
    private int count = 0;
    private static int staticCount = 0;
    private final Object lock = new Object();
​
    /**
     * 1. 修饰实例方法:锁的是this对象
     */
    public synchronized void increment() {
        count++;
    }
​
    /**
     * 2. 修饰静态方法:锁的是Class对象
     */
    public synchronized static void staticIncrement() {
        staticCount++;
    }
​
    /**
     * 3. 修饰代码块:锁的是指定对象
     */
    public void blockIncrement() {
        synchronized (lock) {
            count++;
        }
    }
​
    /**
     * 修复线程安全问题
     */
    public static void main(String[] args) throws Exception {
        SynchronizedDemo demo = new SynchronizedDemo();
​
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    demo.increment();  // 线程安全
                }
            });
            threads[i].start();
        }
​
        for (Thread t : threads) {
            t.join();
        }
​
        // 结果一定是100000
        System.out.println("count = " + demo.count);
    }
}

6.2 synchronized的特性

1. 互斥性:同一时刻只有一个线程能持有锁
2. 可重入性:同一线程可以重复获取同一把锁(不会自己死锁)
3. 非公平性:不保证等待时间最长的线程先获取锁
4. 自动释放:退出synchronized块或方法时自动释放锁
/**
 * 可重入性演示
 */
public class ReentrantDemo {
​
    private synchronized void methodA() {
        System.out.println("methodA");
        methodB();  // 同一线程可以再次获取this锁
    }
​
    private synchronized void methodB() {
        System.out.println("methodB");
    }
    // 如果synchronized不可重入,methodA调用methodB时就会死锁
}

7. volatile关键字

volatile保证变量的可见性有序性,但不保证原子性。

7.1 可见性

/**
 * volatile保证可见性
 */
public class VolatileDemo {
​
    // 不加volatile:线程B可能永远看不到flag的变化
    // 加了volatile:线程B能立即看到flag的变化
    private static volatile boolean flag = false;
​
    public static void main(String[] args) {
        // 线程A:修改flag
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true;
            System.out.println("flag已修改为true");
        }).start();
​
        // 线程B:读取flag
        new Thread(() -> {
            while (!flag) {
                // 不加volatile:可能永远在这里循环(线程B读到的是缓存中的旧值)
                // 加了volatile:flag变化后线程B能立即看到
            }
            System.out.println("检测到flag变化");
        }).start();
    }
}

7.2 volatile不保证原子性

/**
 * volatile不保证原子性
 */
public class VolatileAtomicDemo {
​
    private static volatile int count = 0;
​
    public static void main(String[] args) throws Exception {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    count++;  // 即使加了volatile,结果仍然不正确
                }
            });
            threads[i].start();
        }
​
        for (Thread t : threads) {
            t.join();
        }
​
        // 结果不一定是100000
        // volatile只保证可见性,不保证count++的原子性
        System.out.println("count = " + count);
    }
}

7.3 volatile适用场景

适用:
  1. 状态标志(如上面的flag示例)
  2. 双重检查锁定的单例模式
  3. 读多写少的场景
​
不适用:
  1. 需要原子性的操作(如count++)
  2. 复合操作(如 check-then-act)

7.4 双重检查锁定单例

/**
 * volatile在单例模式中的应用
 */
public class Singleton {
​
    // volatile防止指令重排序
    // new Singleton() 实际上分三步:
    // 1. 分配内存空间
    // 2. 初始化对象
    // 3. 将引用指向内存空间
    // 如果不加volatile,步骤2和3可能重排,导致其他线程拿到未初始化的对象
    private static volatile Singleton instance;
​
    private Singleton() {}
​
    public static Singleton getInstance() {
        if (instance == null) {              // 第一次检查(不加锁)
            synchronized (Singleton.class) {
                if (instance == null) {      // 第二次检查(加锁)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

8. ReentrantLock

ReentrantLock是java.util.concurrent包中的锁,比synchronized更灵活。

8.1 基本用法

import java.util.concurrent.locks.ReentrantLock;
​
public class ReentrantLockDemo {
​
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;
​
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();  // 必须在finally中释放
        }
    }
​
    /**
     * 尝试获取锁(非阻塞)
     */
    public boolean tryIncrement() {
        if (lock.tryLock()) {
            try {
                count++;
                return true;
            } finally {
                lock.unlock();
            }
        }
        return false;
    }
​
    /**
     * 带超时的尝试获取锁
     */
    public boolean tryIncrementWithTimeout() throws InterruptedException {
        if (lock.tryLock(3, java.util.concurrent.TimeUnit.SECONDS)) {
            try {
                count++;
                return true;
            } finally {
                lock.unlock();
            }
        }
        return false;
    }
}

8.2 公平锁与非公平锁

// 非公平锁(默认):新来的线程可以插队,吞吐量高
ReentrantLock unfairLock = new ReentrantLock(false);
​
// 公平锁:严格按照请求顺序获取锁,吞吐量低
ReentrantLock fairLock = new ReentrantLock(true);

8.3 可中断锁

public class InterruptibleLockDemo {
​
    private final ReentrantLock lock = new ReentrantLock();
​
    public void doWork() throws InterruptedException {
        // lockInterruptibly():等待锁的过程中可以被中断
        lock.lockInterruptibly();
        try {
            // 业务逻辑
        } finally {
            lock.unlock();
        }
    }
​
    public static void main(String[] args) {
        InterruptibleLockDemo demo = new InterruptibleLockDemo();
​
        Thread t1 = new Thread(() -> {
            try {
                demo.doWork();
            } catch (InterruptedException e) {
                System.out.println("t1被中断,放弃获取锁");
            }
        });
​
        Thread t2 = new Thread(() -> {
            try {
                demo.doWork();
            } catch (InterruptedException e) {
                System.out.println("t2被中断,放弃获取锁");
            }
        });
​
        t1.start();
        t2.start();
​
        // 中断t2
        t2.interrupt();
    }
}

8.4 Condition条件变量

import java.util.concurrent.locks.*;
import java.util.LinkedList;
import java.util.Queue;
​
/**
 * Condition实现生产者-消费者
 * 比wait/notify更灵活,可以创建多个条件
 */
public class ConditionDemo {
​
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();   // 队列未满
    private final Condition notEmpty = lock.newCondition();  // 队列非空
    private final Queue<Integer> queue = new LinkedList<>();
    private static final int MAX_SIZE = 5;
​
    // 生产者
    public void produce(int value) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() >= MAX_SIZE) {
                notFull.await();  // 队列满了,等待"未满"条件
            }
            queue.offer(value);
            System.out.println("生产: " + value);
            notEmpty.signal();  // 通知消费者
        } finally {
            lock.unlock();
        }
    }
​
    // 消费者
    public int consume() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                notEmpty.await();  // 队列空了,等待"非空"条件
            }
            int value = queue.poll();
            System.out.println("消费: " + value);
            notFull.signal();  // 通知生产者
            return value;
        } finally {
            lock.unlock();
        }
    }
}

9. 线程通信

9.1 使用BlockingQueue(最推荐)

import java.util.concurrent.*;
​
/**
 * 使用BlockingQueue实现生产者-消费者
 * BlockingQueue内部已经实现了线程同步,不需要手动加锁
 */
public class BlockingQueueDemo {
​
    public static void main(String[] args) {
        // 有界阻塞队列
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
​
        // 生产者
        Thread producer = new Thread(() -> {
            int value = 0;
            try {
                while (true) {
                    queue.put(value);  // 队列满时自动阻塞
                    System.out.println("生产: " + value);
                    value++;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
​
        // 消费者
        Thread consumer = new Thread(() -> {
            try {
                while (true) {
                    int value = queue.take();  // 队列空时自动阻塞
                    System.out.println("消费: " + value);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
​
        producer.start();
        consumer.start();
    }
}

9.2 常用BlockingQueue实现

实现类 特点 使用场景
ArrayBlockingQueue 有界数组,公平/非公平可选 固定大小的缓冲区
LinkedBlockingQueue 有界链表,默认非公平 吞吐量要求高的场景
PriorityBlockingQueue 无界优先级队列 需要按优先级处理任务
SynchronousQueue 不存储元素,直接传递 直接交付场景
DelayQueue 延迟获取,元素到期才能取 定时任务、缓存过期

10. 线程池

线程池是Java并发编程中最核心的组件,生产环境必须使用线程池。

10.1 为什么需要线程池

不使用线程池的问题:
​
  每个任务创建一个新线程 → 线程创建销毁开销大
  10000个任务 → 10000个线程 → 内存溢出
  无法控制并发数量 → 可能把服务器压垮
​
线程池的好处:
​
  1. 复用线程,减少创建销毁开销
  2. 控制并发数量,防止资源耗尽
  3. 统一管理、监控线程
  4. 提供任务队列、拒绝策略等功能

10.2 创建线程池

import java.util.concurrent.*;

public class ThreadPoolDemo {

    public static void main(String[] args) {

        // ============ 方式1:使用Executors工具类(不推荐用于生产) ============

        // 固定大小线程池
        ExecutorService fixedPool = Executors.newFixedThreadPool(3);

        // 缓存线程池(线程数不限,空闲线程60秒后回收)
        ExecutorService cachedPool = Executors.newCachedThreadPool();

        // 单线程池(保证任务顺序执行)
        ExecutorService singlePool = Executors.newSingleThreadExecutor();

        // 定时线程池
        ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2);


        // ============ 方式2:手动创建ThreadPoolExecutor(推荐) ============

        ThreadPoolExecutor pool = new ThreadPoolExecutor(
            2,                        // 核心线程数
            5,                        // 最大线程数
            60L,                      // 非核心线程空闲存活时间
            TimeUnit.SECONDS,         // 时间单位
            new LinkedBlockingQueue<>(100),  // 任务队列(容量100)
            Executors.defaultThreadFactory(), // 线程工厂
            new ThreadPoolExecutor.AbortPolicy()  // 拒绝策略
        );
    }
}

10.3 ThreadPoolExecutor参数详解

┌─────────────────────────────────────────────────────────────┐
│                  ThreadPoolExecutor工作原理                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  提交任务                                                    │
│     │                                                       │
│     ▼                                                       │
│  当前线程数 < 核心线程数?                                    │
│     │                                                       │
│  是 → 创建核心线程执行任务                                    │
│  否 → 任务队列未满?                                         │
│           │                                                 │
│        是 → 放入任务队列等待                                  │
│        否 → 当前线程数 < 最大线程数?                          │
│                    │                                        │
│                 是 → 创建非核心线程执行任务                    │
│                 否 → 执行拒绝策略                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘
​
参数说明:
  corePoolSize    核心线程数(一直存活,即使空闲)
  maximumPoolSize 最大线程数(核心线程 + 非核心线程)
  keepAliveTime   非核心线程空闲存活时间
  workQueue       任务队列(存放等待执行的任务)
  threadFactory   线程工厂(自定义线程名等)
  handler         拒绝策略(任务队列满且线程数达到最大时的处理方式)

10.4 四种拒绝策略

// 1. AbortPolicy(默认):抛出RejectedExecutionException
new ThreadPoolExecutor.AbortPolicy();
​
// 2. CallerRunsPolicy:由调用线程(提交任务的线程)执行该任务
new ThreadPoolExecutor.CallerRunsPolicy();
​
// 3. DiscardPolicy:默默丢弃任务,不抛异常
new ThreadPoolExecutor.DiscardPolicy();
​
// 4. DiscardOldestPolicy:丢弃队列中最老的任务,然后重新提交当前任务
new ThreadPoolExecutor.DiscardOldestPolicy();

10.5 提交任务

import java.util.concurrent.*;

public class SubmitTaskDemo {

    public static void main(String[] args) throws Exception {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
            2, 5, 60, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(100)
        );

        // 1. execute():提交Runnable,无返回值
        pool.execute(() -> {
            System.out.println(Thread.currentThread().getName() + " 执行任务");
        });

        // 2. submit():提交Callable,返回Future
        Future<String> future = pool.submit(() -> {
            return "任务结果";
        });
        String result = future.get();  // 阻塞等待结果
        System.out.println(result);

        // 3. invokeAll():批量提交,等待全部完成
        List<Callable<String>> tasks = List.of(
            () -> "任务1",
            () -> "任务2",
            () -> "任务3"
        );
        List<Future<String>> futures = pool.invokeAll(tasks);
        for (Future<String> f : futures) {
            System.out.println(f.get());
        }

        // 4. invokeAny():批量提交,返回最先完成的结果
        String firstResult = pool.invokeAny(tasks);
        System.out.println("最先完成: " + firstResult);

        // 关闭线程池
        pool.shutdown();  // 不再接受新任务,等待已提交任务完成
        // pool.shutdownNow();  // 尝试中断所有正在执行的任务
        pool.awaitTermination(60, TimeUnit.SECONDS);  // 等待所有任务完成
    }
}

10.6 线程池配置建议

CPU密集型任务(计算、加密、压缩):
  线程数 = CPU核心数 + 1
  例如:8核CPU → 9个线程
​
IO密集型任务(网络请求、数据库查询、文件读写):
  线程数 = CPU核心数 × 2
  或者:线程数 = CPU核心数 / (1 - 阻塞系数)
  阻塞系数一般在0.8~0.9之间
  例如:8核CPU → 16~80个线程
​
混合型任务:
  计算IO密集型和CPU密集型的比例,取折中值
​
实际项目中:
  通常先设置一个经验值,然后通过压测调整
  建议使用有界队列,防止OOM
// 获取CPU核心数
int cpuCores = Runtime.getRuntime().availableProcessors();
​
// CPU密集型
ThreadPoolExecutor cpuPool = new ThreadPoolExecutor(
    cpuCores + 1, cpuCores + 1, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100)
);
​
// IO密集型
ThreadPoolExecutor ioPool = new ThreadPoolExecutor(
    cpuCores * 2, cpuCores * 2, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100)
);

10.7 线程池监控

ThreadPoolExecutor pool = new ThreadPoolExecutor(
    2, 5, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100)
);

// 核心线程数
pool.getCorePoolSize();

// 最大线程数
pool.getMaximumPoolSize();

// 当前活跃线程数
pool.getActiveCount();

// 已完成任务数
pool.getCompletedTaskCount();

// 总任务数
pool.getTaskCount();

// 队列中的任务数
pool.getQueue().size();

// 池中当前线程数
pool.getPoolSize();

11. 并发容器

11.1 ConcurrentHashMap

import java.util.concurrent.ConcurrentHashMap;
​
/**
 * 线程安全的HashMap
 * JDK8使用CAS + synchronized实现,性能远好于Hashtable和Collections.synchronizedMap
 */
public class ConcurrentHashMapDemo {
​
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
​
        // 基本操作(线程安全)
        map.put("key1", 1);
        map.get("key1");
        map.remove("key1");
​
        // 原子操作
        map.putIfAbsent("key1", 1);        // 不存在才放入
        map.computeIfAbsent("key2", k -> 2); // 不存在才计算
        map.merge("key1", 1, Integer::sum);  // 合并值(原子操作)
​
        // 批量操作(JDK8+)
        map.forEach(10, (key, value) -> {
            System.out.println(key + "=" + value);
        });
​
        // 搜索(JDK8+)
        String result = map.search(10, (key, value) -> {
            if (value > 100) return key;
            return null;
        });
    }
}

11.2 CopyOnWriteArrayList

import java.util.concurrent.CopyOnWriteArrayList;
​
/**
 * 写时复制的ArrayList
 * 写操作时复制整个数组,适合读多写少的场景
 */
public class CopyOnWriteArrayListDemo {
​
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
​
        // 写操作(加锁,复制数组)
        list.add("A");
        list.add("B");
​
        // 读操作(不加锁,直接读)
        String first = list.get(0);
​
        // 迭代时不会抛ConcurrentModificationException
        for (String item : list) {
            System.out.println(item);
        }
    }
}

11.3 并发容器对比

容器 线程安全 适用场景
HashMap 不安全 单线程
Hashtable 安全(全表锁) 不推荐,性能差
Collections.synchronizedMap 安全(全表锁) 不推荐,性能差
ConcurrentHashMap 安全(分段锁/CAS) 并发Map首选
ArrayList 不安全 单线程
Vector 安全(全表锁) 不推荐
CopyOnWriteArrayList 安全(写时复制) 读多写少

12. 原子类

原子类基于CAS(Compare And Swap)实现无锁线程安全。

12.1 基本原子类

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicBoolean;
​
public class AtomicDemo {
​
    // AtomicInteger:原子整数
    private static AtomicInteger count = new AtomicInteger(0);
​
    // AtomicLong:原子长整数
    private static AtomicLong total = new AtomicLong(0L);
​
    // AtomicBoolean:原子布尔
    private static AtomicBoolean flag = new AtomicBoolean(false);
​
    public static void main(String[] args) throws Exception {
​
        // 基本操作
        count.set(10);           // 设置值
        int val = count.get();   // 获取值
        count.incrementAndGet(); // 先加1再获取(++i)
        count.getAndIncrement(); // 先获取再加1(i++)
        count.addAndGet(5);      // 先加5再获取
        count.decrementAndGet(); // 先减1再获取
​
        // CAS操作
        boolean success = count.compareAndSet(10, 20);
        // 如果当前值是10,则更新为20,返回true
        // 如果当前值不是10,则不更新,返回false
​
        // 原子更新
        count.updateAndGet(x -> x * 2);  // 原子地将值翻倍
        count.accumulateAndGet(5, Integer::sum);  // 原子地加5
​
​
        // ============ 解决线程安全问题 ============
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    count.incrementAndGet();  // 线程安全
                }
            });
            threads[i].start();
        }
​
        for (Thread t : threads) {
            t.join();
        }
​
        // 结果一定是100000
        System.out.println("count = " + count.get());
    }
}

12.2 数组原子类

import java.util.concurrent.atomic.AtomicIntegerArray;
​
public class AtomicIntegerArrayDemo {
​
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        AtomicIntegerArray atomicArr = new AtomicIntegerArray(arr);
​
        atomicArr.incrementAndGet(0);  // 第0个元素加1
        atomicArr.addAndGet(1, 10);    // 第1个元素加10
​
        System.out.println(atomicArr.get(0));  // 2
        System.out.println(atomicArr.get(1));  // 12
​
        // 原数组不受影响
        System.out.println(arr[0]);  // 1
    }
}

12.3 引用原子类

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
​
/**
 * AtomicReference:原子引用
 * AtomicStampedReference:带版本号的原子引用(解决ABA问题)
 */
public class AtomicReferenceDemo {
​
    // AtomicReference
    private static AtomicReference<String> ref = new AtomicReference<>("初始值");
​
    // AtomicStampedReference(解决ABA问题)
    private static AtomicStampedReference<Integer> stampedRef =
        new AtomicStampedReference<>(100, 0);  // 初始值100,版本号0
​
    public static void main(String[] args) {
        // AtomicReference
        ref.compareAndSet("初始值", "新值");
​
        // AtomicStampedReference
        int[] stampHolder = new int[1];
        int value = stampedRef.get(stampHolder);  // 获取值和版本号
        int stamp = stampHolder[0];
​
        // CAS同时检查值和版本号
        stampedRef.compareAndSet(100, 200, stamp, stamp + 1);
    }
}

13. CountDownLatch与CyclicBarrier

13.1 CountDownLatch(倒计时门栓)

import java.util.concurrent.CountDownLatch;
​
/**
 * CountDownLatch:允许一个或多个线程等待其他线程完成
 * 一次性使用,计数到0后不能重置
 */
public class CountDownLatchDemo {
​
    public static void main(String[] args) throws Exception {
        // 创建计数器,初始值为3
        CountDownLatch latch = new CountDownLatch(3);
​
        // 启动3个子线程
        for (int i = 1; i <= 3; i++) {
            final int taskId = i;
            new Thread(() -> {
                try {
                    Thread.sleep((long) (Math.random() * 3000));
                    System.out.println("任务" + taskId + " 完成");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();  // 计数减1
                }
            }).start();
        }
​
        // 主线程等待计数器归零
        latch.await();  // 阻塞直到计数为0
        System.out.println("所有任务完成,继续执行");
    }
}

13.2 CyclicBarrier(循环屏障)

import java.util.concurrent.CyclicBarrier;
​
/**
 * CyclicBarrier:所有线程到达屏障点后一起继续执行
 * 可以重复使用(reset)
 */
public class CyclicBarrierDemo {
​
    public static void main(String[] args) {
        // 3个线程都到达屏障后,执行barrierAction
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            System.out.println("所有线程已就绪,开始执行!");
        });
​
        for (int i = 1; i <= 3; i++) {
            final int threadId = i;
            new Thread(() -> {
                try {
                    System.out.println("线程" + threadId + " 准备就绪");
                    barrier.await();  // 等待其他线程
                    System.out.println("线程" + threadId + " 开始执行");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

13.3 对比

对比项 CountDownLatch CyclicBarrier
计数方式 计数递减到0 计数递增到目标值
可重用 不可以 可以(reset)
等待方 一个或多个线程等待 所有线程互相等待
触发条件 计数为0 所有线程到达屏障点
典型场景 主线程等待子线程完成 多线程同时开始执行

14. CompletableFuture异步编程

CompletableFuture是JDK8引入的异步编程工具,支持链式调用、组合、异常处理。

14.1 基本用法

import java.util.concurrent.CompletableFuture;
​
public class CompletableFutureDemo {
​
    public static void main(String[] args) throws Exception {
​
        // 1. 异步执行(无返回值)
        CompletableFuture<Void> voidFuture = CompletableFuture.runAsync(() -> {
            System.out.println("异步执行: " + Thread.currentThread().getName());
        });
​
        // 2. 异步执行(有返回值)
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            return "异步结果";
        });
​
        // 获取结果
        String result = future.get();  // 阻塞等待
        System.out.println(result);
​
        // 3. 使用自定义线程池
        java.util.concurrent.ExecutorService pool =
            java.util.concurrent.Executors.newFixedThreadPool(4);
​
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            return "自定义线程池结果";
        }, pool);  // 指定线程池
​
        System.out.println(future2.get());
        pool.shutdown();
    }
}

14.2 链式调用

import java.util.concurrent.CompletableFuture;
​
public class ChainDemo {
​
    public static void main(String[] args) throws Exception {
​
        // thenApply:转换结果(同步)
        CompletableFuture<String> future1 = CompletableFuture
            .supplyAsync(() -> 100)
            .thenApply(value -> value * 2)        // 200
            .thenApply(value -> "结果: " + value); // "结果: 200"
​
        System.out.println(future1.get());
​
        // thenApplyAsync:转换结果(异步)
        CompletableFuture<String> future2 = CompletableFuture
            .supplyAsync(() -> "hello")
            .thenApplyAsync(s -> s.toUpperCase());  // 在新线程中执行
​
        System.out.println(future2.get());
​
        // thenAccept:消费结果(无返回值)
        CompletableFuture<Void> future3 = CompletableFuture
            .supplyAsync(() -> "hello")
            .thenAccept(s -> System.out.println("收到: " + s));
​
        // thenRun:执行后续动作(不关心前面的结果)
        CompletableFuture<Void> future4 = CompletableFuture
            .supplyAsync(() -> "hello")
            .thenRun(() -> System.out.println("前面的任务完成了"));
​
        future4.get();
    }
}

14.3 组合多个异步任务

import java.util.concurrent.CompletableFuture;
​
public class CombineDemo {
​
    public static void main(String[] args) throws Exception {
​
        // ============ thenCompose:串行组合(第二个任务依赖第一个的结果) ============
        CompletableFuture<String> composed = CompletableFuture
            .supplyAsync(() -> "userId")
            .thenCompose(userId -> getUserOrders(userId));  // 用userId查询订单
​
        System.out.println(composed.get());
​
​
        // ============ thenCombine:并行组合(两个任务同时执行,合并结果) ============
        CompletableFuture<Double> combined = CompletableFuture
            .supplyAsync(() -> 100.0)  // 任务1:获取价格
            .thenCombine(
                CompletableFuture.supplyAsync(() -> 0.8),  // 任务2:获取折扣
                (price, discount) -> price * discount       // 合并:计算折后价
            );
​
        System.out.println("折后价: " + combined.get());
​
​
        // ============ allOf:等待所有任务完成 ============
        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> "任务1");
        CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> "任务2");
        CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> "任务3");
​
        CompletableFuture.allOf(task1, task2, task3).join();  // 等待全部完成
        System.out.println(task1.get() + ", " + task2.get() + ", " + task3.get());
​
​
        // ============ anyOf:任意一个完成即可 ============
        CompletableFuture<Object> anyResult = CompletableFuture.anyOf(task1, task2, task3);
        System.out.println("最先完成: " + anyResult.get());
    }
​
    static CompletableFuture<String> getUserOrders(String userId) {
        return CompletableFuture.supplyAsync(() -> userId + "的订单列表");
    }
}

14.4 异常处理

import java.util.concurrent.CompletableFuture;
​
public class ExceptionDemo {
​
    public static void main(String[] args) throws Exception {
​
        // handle:处理结果或异常
        CompletableFuture<String> future1 = CompletableFuture
            .supplyAsync(() -> {
                if (true) throw new RuntimeException("出错了");
                return "正常结果";
            })
            .handle((result, ex) -> {
                if (ex != null) {
                    return "默认值(异常: " + ex.getMessage() + ")";
                }
                return result;
            });
​
        System.out.println(future1.get());
​
​
        // exceptionally:只处理异常
        CompletableFuture<String> future2 = CompletableFuture
            .supplyAsync(() -> {
                throw new RuntimeException("出错了");
            })
            .exceptionally(ex -> {
                return "降级结果";
            });
​
        System.out.println(future2.get());
​
​
        // whenComplete:回调(不改变结果)
        CompletableFuture<String> future3 = CompletableFuture
            .supplyAsync(() -> "正常结果")
            .whenComplete((result, ex) -> {
                if (ex != null) {
                    System.out.println("异常: " + ex.getMessage());
                } else {
                    System.out.println("成功: " + result);
                }
            });
​
        System.out.println(future3.get());
    }
}

14.5 实战:并行调用多个接口

import java.util.concurrent.CompletableFuture;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 实战场景:同时调用多个微服务接口,汇总结果
 */
public class ParallelCallDemo {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();

        // 并行调用3个接口
        CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {
            sleep(1000);  // 模拟调用用户服务
            return "用户信息";
        });

        CompletableFuture<String> orderFuture = CompletableFuture.supplyAsync(() -> {
            sleep(1500);  // 模拟调用订单服务
            return "订单信息";
        });

        CompletableFuture<String> productFuture = CompletableFuture.supplyAsync(() -> {
            sleep(800);   // 模拟调用商品服务
            return "商品信息";
        });

        // 等待全部完成
        CompletableFuture.allOf(userFuture, orderFuture, productFuture).join();

        // 汇总结果
        String result = String.join(", ",
            userFuture.get(), orderFuture.get(), productFuture.get());

        long cost = System.currentTimeMillis() - start;
        System.out.println("结果: " + result);
        System.out.println("耗时: " + cost + "ms");
        // 串行需要 1000+1500+800 = 3300ms
        // 并行只需 max(1000,1500,800) ≈ 1500ms
    }

    private static void sleep(long millis) {
        try { Thread.sleep(millis); } catch (InterruptedException e) { }
    }
}

15. ThreadLocal

ThreadLocal为每个线程提供独立的变量副本,实现线程隔离。

15.1 基本用法

public class ThreadLocalDemo {
​
    // 每个线程有独立的副本
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
​
    public static void main(String[] args) {
        // 线程A
        new Thread(() -> {
            threadLocal.set(100);
            System.out.println("线程A: " + threadLocal.get());  // 100
            threadLocal.remove();  // 用完必须remove,防止内存泄漏
        }).start();
​
        // 线程B
        new Thread(() -> {
            threadLocal.set(200);
            System.out.println("线程B: " + threadLocal.get());  // 200
            threadLocal.remove();
        }).start();
​
        // 主线程
        System.out.println("主线程: " + threadLocal.get());  // 0(默认值)
    }
}

15.2 典型应用:用户信息传递

/**
 * 使用ThreadLocal在请求处理链路中传递用户信息
 * 例如:在Controller中设置,在Service中获取
 */
public class UserContext {
​
    private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
​
    public static void setUser(User user) {
        currentUser.set(user);
    }
​
    public static User getUser() {
        return currentUser.get();
    }
​
    public static void clear() {
        currentUser.remove();
    }
}
​
// 拦截器中设置
public class AuthInterceptor implements HandlerInterceptor {
​
    @Override
    public boolean preHandle(HttpServletRequest request, ...) {
        String token = request.getHeader("Authorization");
        User user = validateToken(token);
        UserContext.setUser(user);  // 设置到ThreadLocal
        return true;
    }
​
    @Override
    public void afterCompletion(...) {
        UserContext.clear();  // 请求结束必须清理
    }
}
​
// Service中使用
@Service
public class OrderService {
​
    public void createOrder() {
        User user = UserContext.getUser();  // 直接获取当前用户
        // 创建订单...
    }
}

15.3 ThreadLocal内存泄漏

ThreadLocal内存泄漏的原因:
​
  Thread对象中有一个ThreadLocalMap
  key是ThreadLocal对象(弱引用)
  value是我们设置的值(强引用)
​
  当ThreadLocal对象被GC回收后:
    key变成null
    但value仍然被强引用,无法被GC回收
    → 内存泄漏!
​
解决方案:
  使用完毕后调用 threadLocal.remove()
  特别是在线程池中,线程会被复用,必须手动清理

16. 实战:多线程下载文件

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.*;
​
/**
 * 多线程下载文件
 * 将文件分成多个块,每个线程下载一个块,最后合并
 */
public class MultiThreadDownload {
​
    private static final int THREAD_COUNT = 4;  // 线程数
    private static final String FILE_URL = "https://example.com/large-file.zip";
    private static final String OUTPUT_FILE = "large-file.zip";
​
    public static void main(String[] args) throws Exception {
        // 1. 获取文件大小
        long fileSize = getFileSize(FILE_URL);
        System.out.println("文件大小: " + fileSize + " bytes");
​
        // 2. 计算每个线程下载的范围
        long blockSize = fileSize / THREAD_COUNT;
​
        // 3. 创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(THREAD_COUNT);
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
​
        // 4. 启动下载
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < THREAD_COUNT; i++) {
            final int threadId = i;
            final long startPos = i * blockSize;
            final long endPos = (i == THREAD_COUNT - 1) ? fileSize - 1 : (i + 1) * blockSize - 1;
​
            pool.execute(() -> {
                try {
                    downloadBlock(FILE_URL, startPos, endPos, threadId);
                    System.out.println("线程" + threadId + " 下载完成");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            });
        }
​
        // 5. 等待所有线程完成
        latch.await();
        pool.shutdown();
​
        // 6. 合并文件
        mergeFiles(THREAD_COUNT, OUTPUT_FILE);
​
        long cost = System.currentTimeMillis() - startTime;
        System.out.println("下载完成,耗时: " + cost + "ms");
    }
​
    /**
     * 下载文件的一部分
     */
    private static void downloadBlock(String url, long start, long end, int threadId)
            throws IOException {
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
        conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
​
        try (InputStream is = conn.getInputStream();
             RandomAccessFile raf = new RandomAccessFile(OUTPUT_FILE + ".part" + threadId, "rw")) {
            byte[] buffer = new byte[8192];
            int len;
            while ((len = is.read(buffer)) != -1) {
                raf.write(buffer, 0, len);
            }
        }
    }
​
    /**
     * 合并分块文件
     */
    private static void mergeFiles(int threadCount, String outputFile) throws IOException {
        try (OutputStream os = new FileOutputStream(outputFile)) {
            for (int i = 0; i < threadCount; i++) {
                File partFile = new File(outputFile + ".part" + i);
                try (InputStream is = new FileInputStream(partFile)) {
                    byte[] buffer = new byte[8192];
                    int len;
                    while ((len = is.read(buffer)) != -1) {
                        os.write(buffer, 0, len);
                    }
                }
                partFile.delete();  // 删除分块文件
            }
        }
    }
​
    private static long getFileSize(String url) throws IOException {
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
        conn.setRequestMethod("HEAD");
        return conn.getContentLengthLong();
    }
}

17. 常见面试题精选

Q1:创建线程的方式有几种?

4种:
1. 继承Thread类
2. 实现Runnable接口
3. 实现Callable接口(有返回值)
4. 线程池创建(生产环境首选)
​
本质上只有两种:继承Thread和实现Runnable/Callable
线程池底层也是基于Runnable/Callable

Q2:sleep()和wait()的区别?

1. sleep()是Thread的方法,wait()是Object的方法
2. sleep()不释放锁,wait()释放锁
3. sleep()到时间自动恢复,wait()需要notify()/notifyAll()唤醒
4. sleep()可以在任何地方使用,wait()必须在synchronized块中使用

Q3:synchronized和ReentrantLock的区别?

1. synchronized是关键字,ReentrantLock是类
2. synchronized自动释放锁,ReentrantLock手动unlock(finally中)
3. ReentrantLock支持可中断、超时、公平锁、多条件变量
4. synchronized有锁升级优化
5. 简单场景用synchronized,需要高级特性用ReentrantLock

Q4:线程池的核心参数有哪些?

7个参数:
1. corePoolSize      核心线程数
2. maximumPoolSize   最大线程数
3. keepAliveTime     非核心线程空闲存活时间
4. unit              时间单位
5. workQueue         任务队列
6. threadFactory     线程工厂
7. handler           拒绝策略

Q5:volatile和synchronized的区别?

1. volatile保证可见性和有序性,不保证原子性
   synchronized保证原子性、可见性和有序性
2. volatile只能修饰变量,synchronized可以修饰方法和代码块
3. volatile不会阻塞线程,synchronized会阻塞线程
4. volatile更轻量级,性能更好

Q6:CountDownLatch和CyclicBarrier的区别?

1. CountDownLatch计数递减,CyclicBarrier计数递增
2. CountDownLatch不可重用,CyclicBarrier可以reset重用
3. CountDownLatch是一个或多个线程等待其他线程
   CyclicBarrier是所有线程互相等待

Q7:ThreadLocal为什么会内存泄漏?

ThreadLocalMap的key是弱引用,value是强引用
当ThreadLocal对象被GC回收后,key变成null
但value仍然被强引用,无法被GC回收
→ 内存泄漏
​
解决方案:使用完毕后调用remove()

18. 总结

知识体系

┌─────────────────────────────────────────────────────────────┐
│                    Java多线程知识体系                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │  线程基础    │  │  线程安全    │  │  线程协作    │         │
│  │  创建方式    │  │  synchronized│  │  wait/notify│         │
│  │  生命周期    │  │  volatile   │  │  CountDownL.│         │
│  │  常用方法    │  │  ReentrantL.│  │  CyclicBar. │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
│                                                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │  线程池      │  │  并发容器    │  │  异步编程    │         │
│  │  ThreadPool │  │  ConcHashMap│  │  CompletableFuture│    │
│  │  参数/策略   │  │  CopyOnWrite│  │  链式/组合    │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
│                                                             │
│  ┌─────────────┐  ┌─────────────┐                          │
│  │  原子类      │  │  ThreadLocal│                          │
│  │  CAS        │  │  线程隔离    │                          │
│  │  ABA问题    │  │  内存泄漏    │                          │
│  └─────────────┘  └─────────────┘                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

最佳实践

1. 生产环境必须使用线程池,不要手动创建线程
2. 线程池使用ThreadPoolExecutor,不要用Executors
3. 线程池使用有界队列,防止OOM
4. 简单同步用synchronized,高级需求用ReentrantLock
5. 读多写少用volatile或CopyOnWriteArrayList
6. 并发Map用ConcurrentHashMap,不要用Hashtable
7. ThreadLocal用完必须remove,防止内存泄漏
8. 异步编程优先使用CompletableFuture
9. 不要在锁内做耗时操作(IO、远程调用)
10. 优先使用并发工具类(BlockingQueue、CountDownLatch等),而不是手动wait/notify

一句话总结

线程是CPU调度的基本单位,线程池管理线程的生命周期,synchronized/volatile/ReentrantLock保证线程安全,CompletableFuture简化异步编程,ThreadLocal实现线程隔离。掌握这些,就掌握了Java并发编程的核心。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐