「动力节点」专项爆破Java多线程与并发编程(吊打面试官)

83 阅读5分钟

Java多线程编程核心:从Thread到Executor框架的全面解析

一、Java多线程基础概念

Java多线程编程是现代软件开发中不可或缺的核心技术,它允许程序在同一时间内执行多个任务,显著提高系统资源利用率和程序响应速度。多线程编程的本质是通过创建多个执行路径(线程)来并发处理任务,这些线程共享进程的内存空间,但拥有独立的程序计数器、栈和局部变量。

1.1 线程与进程的区别

  • 进程:操作系统资源分配的基本单位,拥有独立的内存空间
  • 线程:CPU调度的基本单位,共享进程的内存空间,创建和切换开销更小

1.2 Java线程模型

Java提供了两种创建线程的基本方式:

  1. 继承Thread类并重写run方法
  2. 实现Runnable接口

这两种方式各有优缺点:继承Thread类的方式简单直接,但Java不支持多重继承;实现Runnable接口的方式更加灵活,适合需要继承其他类的场景。

二、线程同步与线程安全

当多个线程同时访问共享资源时,可能会出现竞态条件、内存可见性问题和指令重排序问题。Java提供了多种同步机制来解决这些问题。

2.1 同步机制

2.1.1 锁机制

  • synchronized关键字:Java内置的同步机制,可用于方法或代码块
  • ReentrantLock:JUC包提供的可重入锁,比synchronized更灵活
  • ReadWriteLock:读写分离锁,提高读多写少场景的性能

2.1.2 原子类

java.util.concurrent.atomic包提供了一系列原子类,如AtomicInteger、AtomicLong等,这些类通过CAS(Compare-And-Swap)操作保证原子性。

2.1.3 volatile关键字

volatile保证变量的可见性,强制线程每次读取变量都从主内存获取,而不是使用本地缓存。

三、Executor框架概述

Executor框架是Java提供的一套用于并发任务管理的线程池框架,它的核心理念是任务提交与任务执行的解耦。用个比喻:你去打印店打印文件,把文件交给店员(Executor),店员把它安排给打印机(线程)处理,你不需要知道哪个打印机在打、什么时候打完,你只要管交活就行了。

3.1 Executor框架的优势

  1. 资源管理优化:避免频繁创建和销毁线程的开销
  2. 提高响应速度:任务到达时可以直接执行,无需等待线程创建
  3. 提高线程可管理性:可以统一分配、调优和监控线程
  4. 提供更多功能:支持定时执行、定期执行等功能

3.2 Executor框架的核心接口

Executor框架主要由3大部分组成:

  1. Executor接口:是Executor框架的顶级接口,定义了一个用于执行任务的方法execute(Runnable command)
  2. ExecutorService接口:继承自Executor接口,是线程池的主要接口。它扩展了Executor接口,并添加了一些管理线程池的方法,如提交任务、关闭线程池等
  3. ThreadPoolExecutor类:ExecutorService接口的默认实现,提供了可配置的线程池实现

四、线程池详解

4.1 线程池核心参数

ThreadPoolExecutor类提供了灵活的线程池配置,主要参数包括:

  • corePoolSize:核心线程数,线程池长期维持的最小线程数
  • maximumPoolSize:允许创建的最大线程数
  • keepAliveTime:空闲线程存活时间
  • workQueue:工作队列,存储待执行任务
  • handler:拒绝策略,当任务无法处理时的处理方式

4.2 常见线程池类型

Executors工厂类提供了几种预定义的线程池:

  1. newFixedThreadPool:创建固定大小的线程池
  2. newSingleThreadExecutor:创建单一线程的线程池
  3. newCachedThreadPool:创建可缓存的线程池,调用execute将重用以前构造的线程
  4. newScheduledThreadPool:支持延迟任务和周期性任务的线程池

4.3 线程池工作流程

  1. 当提交一个新任务时,线程池首先检查当前运行的线程数是否小于corePoolSize
  2. 如果小于,则创建新线程执行任务
  3. 如果大于等于,则将任务放入工作队列
  4. 如果工作队列已满且当前线程数小于maximumPoolSize,则创建新线程执行任务
  5. 如果工作队列已满且当前线程数等于maximumPoolSize,则执行拒绝策略

五、Executor框架高级特性

5.1 Callable与Future

Executor框架不仅支持Runnable任务,还支持Callable任务。Callable与Runnable的主要区别在于:

  • Callable可以返回结果
  • Callable可以抛出异常
  • Callable需要使用ExecutorService的submit方法提交

Future接口表示异步计算的结果,提供了检查计算是否完成、等待计算完成以及获取计算结果的方法。

5.2 ScheduledExecutorService

ScheduledExecutorService接口继承自ExecutorService接口,用于支持延迟任务和周期性任务。它提供了两种主要方法:

  1. schedule:延迟执行一次任务
  2. scheduleAtFixedRate:以固定频率周期性执行任务
  3. scheduleWithFixedDelay:以固定延迟周期性执行任务

六、多线程编程最佳实践

  1. 优先使用线程池:避免直接创建线程,使用Executors工厂方法或自定义ThreadPoolExecutor
  2. 合理设置线程池大小:CPU密集型任务建议线程数为CPU核心数+1,IO密集型任务可适当增大
  3. 使用合适的拒绝策略:根据业务需求选择AbortPolicy、CallerRunsPolicy、DiscardPolicy或DiscardOldestPolicy
  4. 注意线程安全:合理使用同步机制,避免死锁和资源竞争
  5. 避免过度同步:缩小同步范围,减少锁竞争
  6. 使用不可变对象:不可变对象天然线程安全

七、总结

从Thread到Executor框架,Java多线程编程经历了从基础到高级的演进过程。Executor框架通过线程池技术,解决了直接使用Thread类时面临的资源管理问题,提供了更高效、更灵活的并发编程模型。掌握这些核心技术,是Java开发者向高级进阶的必经之路。在实际开发中,应根据具体场景选择合适的并发策略,并遵循多线程编程的最佳实践,才能在保证程序正确性的同时,充分发挥多核处理器的性能优势。