JAVA多线程

一、线程与进程:操作系统资源管理的核心概念

在操作系统的资源管理体系中,进程与线程是两个关键概念。进程是操作系统分配资源的最小单位,每个进程拥有独立的内存空间、文件句柄等资源,进程间通过内存隔离保证安全性。而线程是 CPU 调度的最小单位,同一进程内的多个线程共享进程资源(如内存、全局变量),这使得线程间通信更高效,但也带来了共享资源竞争的问题。相较于进程,线程具有更轻量的特性,上下文切换成本更低,因此多线程编程成为提升 CPU 利用率的重要手段。

二、Java 线程的创建与生命周期管理

(一)线程创建的三种方式

  1. 继承 Thread 类
    通过创建 Thread 子类并重写 run () 方法实现线程逻辑:
public class MyThread extends Thread {
    @Override
    public void run() {
        // 业务逻辑
    }
}
new MyThread().start(); // 启动线程

此方式简单直观,但受限于 Java 单继承机制,扩展性较差。

  1. 实现 Runnable 接口(推荐)
    分离线程逻辑与线程对象,更符合面向接口编程原则:
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 业务逻辑
    }
}
new Thread(new MyRunnable()).start();

该方式避免了单继承限制,便于代码复用。

  1. 使用 Callable+Future(支持返回值与异常处理)
    适用于需要异步计算结果的场景:
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
    return 100; // 可返回计算结果
});
new Thread(futureTask).start();
Integer result = futureTask.get(); // 阻塞获取结果

(二)线程生命周期的六个阶段

  1. 新建(New):线程对象已创建,但尚未调用 start () 方法。
  2. 就绪(Runnable):调用 start () 后进入就绪队列,等待 CPU 调度。
  3. 运行(Running):获取 CPU 执行权,执行 run () 方法。
  4. 阻塞(Blocked/Waiting/Timed Waiting)
    • Blocked:等待锁(如 synchronized 同步块)。
    • Waiting:调用 Object.wait () 或 LockSupport.park (),需其他线程唤醒。
    • Timed Waiting:调用 Thread.sleep (long) 等带超时参数的方法,超时后自动恢复。
  5. 终止(Terminated):run () 方法正常执行完毕或因异常退出。

三、线程同步与并发控制:解决共享资源问题

(一)共享资源的三大核心问题

  1. 原子性:操作不可中断(如 i++ 是非原子操作,需加锁保证)。
  2. 可见性:线程修改共享变量后,其他线程能否及时感知(volatile、synchronized、Lock 可保证)。
  3. 有序性:编译器或处理器优化可能导致指令重排序(volatile 通过内存屏障禁止重排序)。

(二)同步机制:锁的实现与对比

  1. synchronized 关键字(JVM 内置锁)

    • 作用范围
      • 实例方法:锁定当前实例对象。
      • 静态方法:锁定当前类的 Class 对象。
      • 代码块:锁定指定对象(如 synchronized (this))。
    • 原理:基于 Monitor 对象实现,竞争激烈时会从偏向锁升级为重量级锁,性能开销较大。
  2. ReentrantLock(JUC 显式锁)

ReentrantLock lock = new ReentrantLock();
lock.lock(); // 加锁(支持响应中断、尝试加锁)
try {
    // 业务逻辑
} finally {
    lock.unlock(); // 必须在finally中释放锁
}

特点:支持公平锁 / 非公平锁、条件变量(Condition)、可中断加锁,适用于复杂同步场景。

对比总结

特性synchronizedReentrantLock
锁获取方式自动加解锁手动加解锁
公平性非公平可设置公平性
条件变量支持(Condition)
锁升级机制有(偏向锁→重量级)

(三)线程间通信:协作与协调

  1. Object.wait/notify/notifyAll
    需配合 synchronized 使用,实现线程间的等待 - 通知机制:
synchronized (obj) {
    obj.wait(); // 释放锁,进入等待队列
    obj.notify(); // 唤醒一个等待线程
}
  1. LockSupport.park/unpark
    更高效的线程阻塞 / 唤醒工具,避免传统 wait/notify 的死锁隐患:
LockSupport.park(); // 阻塞当前线程
LockSupport.unpark(thread); // 唤醒指定线程

四、JUC 并发工具类:提升开发效率的利器

(一)并发容器:线程安全的数据结构

  1. ConcurrentHashMap(JDK1.8+)
    底层采用 “数组 + 链表 + 红黑树” 结构,通过 CAS 与分段锁(synchronized)实现线程安全,适用于高并发读多写少场景。

  2. CopyOnWriteArrayList
    写时复制机制(修改时创建新数组),读操作无锁,适合读多写少但不要求实时性的场景(如日志记录)。

  3. BlockingQueue(阻塞队列)
    如 ArrayBlockingQueue、LinkedBlockingQueue,支持线程安全的插入 / 取出操作,天然适配生产者 - 消费者模式。

(二)同步辅助类:控制线程协作

  1. CountDownLatch
    允许一个或多个线程等待其他线程完成操作:
CountDownLatch latch = new CountDownLatch(3);
// 子线程完成任务后调用latch.countDown()
latch.await(); // 主线程等待计数归零
  1. CyclicBarrier
    多个线程互相等待,全部到达屏障点后一起执行:
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    System.out.println("所有线程到达屏障点");
});
barrier.await(); // 线程到达后等待其他线程
  1. Semaphore
    控制同时访问资源的线程数量(如限流):
Semaphore semaphore = new Semaphore(2); // 允许2个线程同时访问
semaphore.acquire(); // 获取许可(阻塞)
try {
    // 业务逻辑
} finally {
    semaphore.release(); // 释放许可
}

五、线程池:资源管理的核心组件

(一)为什么使用线程池?

  • 避免频繁创建 / 销毁线程的开销,重用线程。
  • 控制线程数量,防止内存溢出(如大量并发请求)。
  • 统一管理线程生命周期,支持定时任务、异步处理等场景。

(二)ThreadPoolExecutor 核心参数与工作流程

public ThreadPoolExecutor(
    int corePoolSize,         // 核心线程数(闲置时不销毁)
    int maximumPoolSize,      // 最大线程数(核心+临时线程)
    long keepAliveTime,       // 临时线程闲置存活时间
    TimeUnit unit,            // 时间单位
    BlockingQueue<Runnable> workQueue, // 任务队列
    ThreadFactory threadFactory, // 线程工厂
    RejectedExecutionHandler handler // 拒绝策略
);

工作流程

  1. 提交任务,若核心线程数未满,创建新线程执行任务。
  2. 若核心线程已满,任务进入队列(workQueue)。
  3. 若队列已满且线程数未达 maximumPoolSize,创建临时线程执行任务。
  4. 若线程数达上限且队列已满,触发拒绝策略。

(三)拒绝策略(RejectedExecutionHandler)

  • AbortPolicy(默认):抛出 RejectedExecutionException,终止任务。
  • CallerRunsPolicy:由提交任务的线程直接执行任务。
  • DiscardPolicy:静默丢弃任务,无任何提示。
  • DiscardOldestPolicy:丢弃队列中最老的任务,尝试提交新任务。

(四)常见线程池工厂方法(注意陷阱)

  • Executors.newFixedThreadPool(int nThreads):固定大小线程池,使用无界队列(LinkedBlockingQueue),可能导致 OOM。
  • Executors.newCachedThreadPool():可缓存线程池,线程数无上限,适用于短时间任务。
  • Executors.newScheduledThreadPool(int corePoolSize):定时任务线程池,支持延迟执行和周期性任务。
    最佳实践:优先使用 ThreadPoolExecutor 自定义参数,避免无界队列引发内存问题。

六、Java 内存模型(JMM)与原子操作

(一)JMM 核心架构

  • 主内存(Main Memory):所有线程共享的内存,存储对象实例、静态变量等。
  • 工作内存(Working Memory):每个线程的本地缓存,存储主内存变量的副本。
    交互规则:线程对变量的操作必须先从主内存读取到工作内存,修改后写回主内存,这是可见性问题的根源。

(二)volatile 关键字:可见性与有序性的保证

  • 作用
    1. 强制线程从主内存读取变量,保证修改的可见性。
    2. 通过内存屏障禁止指令重排序,保证有序性。
  • 局限性:不保证原子性(如 volatile int i; i++ 仍需加锁)。

(三)CAS(Compare-And-Swap):无锁原子操作

  • 原理:基于硬件指令(如 CPU 的 cmpxchg),通过 “比较 - 交换” 实现原子更新。
  • 步骤
    1. 读取当前值(V)和预期值(A)。
    2. 若 V 等于 A,更新为新值(B);否则重试。
  • 问题
    • ABA 问题:值从 A→B→A 时,CAS 误认为未修改(解决方案:AtomicStampedReference 添加版本号)。
    • 循环开销:竞争激烈时重试次数多,性能下降。
  • 应用场景:AtomicInteger、ConcurrentHashMap 的底层实现。

七、高级主题:线程安全设计与性能优化

(一)线程安全设计原则

  1. 不可变性(Immutable):如 String、Integer 等类,通过 final 关键字保证状态不可变,避免共享变量被修改。
  2. 线程封闭(Thread Local):使用 ThreadLocal 将变量与线程绑定(如数据库连接、用户上下文):
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("value"); // 仅当前线程可见
  1. 锁分段(Lock Stripping):如 ConcurrentHashMap 对不同分段(Segment)加锁,降低锁粒度,提升并发性能。

(二)性能优化与死锁预防

  • 减少锁竞争
    • 缩小锁范围:同步块内避免无关操作。
    • 使用细粒度锁:如分段锁、ReentrantReadWriteLock 读写锁。
  • 死锁预防
    • 按顺序加锁:所有线程以相同顺序获取锁。
    • 限时加锁:使用 tryLock (long time, TimeUnit unit) 避免永久阻塞。
  • 诊断工具
    • jps:查看 Java 进程 ID。
    • jstack <pid>:打印线程栈,检测死锁、阻塞。
    • VisualVM:图形化监控线程状态、锁竞争。

(三)Fork/Join 框架:并行任务分解

适用于大任务可分解为小任务的场景(如归并排序、文件搜索)。核心类包括 ForkJoinTask(任务抽象类)和 ForkJoinPool(线程池)。
示例:计算 1+2+…+n

public class SumTask extends RecursiveTask<Integer> {
    private int start, end;
    private static final int THRESHOLD = 1000; // 阈值,小于该值直接计算

    public SumTask(int start, int end) { this.start = start; this.end = end; }

    @Override
    protected Integer compute() {
        if (end - start <= THRESHOLD) {
            return IntStream.rangeClosed(start, end).sum();
        } else {
            int mid = (start + end) / 2;
            SumTask leftTask = new SumTask(start, mid);
            SumTask rightTask = new SumTask(mid+1, end);
            leftTask.fork(); // 异步执行左任务
            return rightTask.compute() + leftTask.join(); // 合并结果
        }
    }
}

(四)异步编程:CompletableFuture

简化异步任务的组合、回调和异常处理,支持链式调用与结果合并:

CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> fetchUser());
CompletableFuture<Order> orderFuture = CompletableFuture.supplyAsync(() -> fetchOrder());

CompletableFuture<Void> combinedFuture = userFuture.thenCombine(orderFuture, (user, order) -> {
    process(user, order); // 合并处理用户与订单信息
    return null;
});

总结

Java 多线程编程是提升应用性能的关键技术,但也伴随着共享资源竞争、死锁等复杂问题。通过深入理解线程生命周期、同步机制、JUC 工具类及线程池原理,结合合理的设计原则与性能优化策略,开发者可以编写出高效、安全的并发程序。在实际开发中,需根据具体场景选择合适的并发工具,并借助诊断工具持续优化系统性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值