线程概念
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
线程生命周期


- 新建(New):线程对象被创建但尚未启动 Thread thread = new Thread();
- 就绪(Runnable):线程已准备好运行,等待CPU调度,start()只能调用一次,多次调用会抛出IllegalThreadStateException
- 运行(Running):线程正在执行 cup执行
- 阻塞(Blocked):线程等待某个条件(如I/O操作、锁等)
- 等待:await(),sleep(),join()
- 终止(Terminated):线程执行完毕或异常退出
线程的 start 方法和 run 方法有什么区别?
public class Demo { public static void main(String[] args) throws ExecutionException, InterruptedException { /** * 1.直接重写run() 或继承Thread类再重写run() */ Thread thread = new Thread() { @Override public void run() { System.out.println("Thread"); } }; // 开启线程 thread.start(); /** * 2.lambda、内部类或线程类方式实现Runnable接口,实现run()方法 * 再交给Thread 类 */ Thread runThread = new Thread(() -> { System.out.println("Runnable"); }); // 开启线程 runThread.start(); /** * 3.lambda、内部类或线程类方式实现Callable接口,实现call()方法 * 再交给Thread 类:FutureTask本质也是Runnable实现类 */ FutureTask<String> futureTask = new FutureTask<String>(() -> { System.out.println("Callable"); return "CallableThread"; }); Thread callThread = new Thread(futureTask); // 开启线程 callThread.start(); // 获取call()方法的返回值 String s = futureTask.get(); System.out.println("call()方法的返回值:"+s); } }
线程实现方式
用户级线程:由用户空间的线程库实现
- 优点:不依赖操作系统,切换快
- 缺点:一个线程阻塞会导致整个进程阻塞
public class UserLevelThread { public static void main(String[] args) { // 模拟用户级线程 - 实际Java不直接支持纯用户级线程 Runnable task1 = () -> System.out.println("用户线程1运行"); Runnable task2 = () -> System.out.println("用户线程2运行"); // 在单内核线程上顺序执行 task1.run(); task2.run(); } }
内核级线程:由操作系统内核直接支持
- 优点:一个线程阻塞不会影响其他线程
- 缺点:创建和切换需要系统调用,开销较大
public class KernelThread { public static void main(String[] args) { // 真实内核线程 Thread t1 = new Thread(() -> { System.out.println("内核线程1 ID: " + Thread.currentThread().getId()); }); Thread t2 = new Thread(() -> { System.out.println("内核线程2 ID: " + Thread.currentThread().getId()); }); t1.start(); t2.start(); } }
混合实现:结合两者优点(如Java的线程模型)
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class HybridModel { public static void main(String[] args) { // 线程池实现混合模型 ExecutorService pool = Executors.newFixedThreadPool(2); // 提交多个任务 for (int i = 0; i < 5; i++) { final int taskId = i; pool.execute(() -> { System.out.println("任务" + taskId + "在" + Thread.currentThread().getName() + "执行"); }); } pool.shutdown(); } }
ThreadLocal
1. 一句话总结
ThreadLocal 就像给每个线程发了一个专属的储物柜,每个线程只能存取自己的东西,互相看不到对方柜子里的内容。
2. 生活化比喻
- 场景:公司更衣室
- 每个员工(线程)有自己独立的柜子(ThreadLocal)。
- 员工A 往自己柜子里放了一双鞋,员工B 完全不知道,也拿不到。
- 员工离职(线程结束)时,柜子会被清空(避免内存泄漏)。
3. 解决了什么问题?
- 问题:多线程共享变量时,会打架(线程不安全)。
- 比如:10个线程同时修改同一个变量
count++,结果可能错乱。- ThreadLocal 方案:
- 直接给每个线程发一个独立的
count副本,各自玩自己的,互不干扰。
4. 代码例子(秒懂版)
public class ThreadLocalDemo { // 1. 创建一个ThreadLocal(相当于发储物柜) private static ThreadLocal<String> name = new ThreadLocal<>(); public static void main(String[] args) { // 线程1:存自己的名字 new Thread(() -> { name.set("张三"); // 存到自己的柜子 System.out.println("线程1的名字:" + name.get()); // 只能拿到"张三" }).start(); // 线程2:存自己的名字 new Thread(() -> { name.set("李四"); // 存到自己的柜子 System.out.println("线程2的名字:" + name.get()); // 只能拿到"李四" }).start(); } }核心特点:
- 线程隔离:变量对每个线程独立可见
- 无锁化:天然避免线程安全问题
2. 底层原理
- 数据结构:每个线程(
Thread类)内部维护一个ThreadLocalMap(类似HashMap),键为ThreadLocal实例,值为存储的变量。- 哈希冲突解决:开放地址法(线性探测)。
3. 核心方法
方法 作用 get()获取当前线程的变量副本 set(T value)设置当前线程的变量副本 remove()移除当前线程的变量副本(防止内存泄漏) initialValue()覆盖此方法可定义初始值(默认返回 null)4. 内存泄漏问题
- 原因:
ThreadLocalMap的键是弱引用(WeakReference<ThreadLocal>),但值是强引用。- 若
ThreadLocal实例被回收,但线程未终止,会导致value无法被回收。- 解决方案:
- 显式调用
remove()清理条目。- 使用
static final修饰ThreadLocal实例(延长生命周期)。5. 典型应用场景
- 数据库连接管理:每个线程维护独立的
Connection(如 Spring 的TransactionSynchronizationManager)。- 用户会话信息:存储当前请求的用户身份(如 Spring Security 的
SecurityContextHolder)。- 日期格式化:避免
SimpleDateFormat的线程不安全问题。6. 与同步机制对比
特性 ThreadLocalsynchronized/Lock数据隔离 线程独享 共享数据+同步访问 性能 无锁,更高性能 有锁,可能阻塞 适用场景 线程间数据隔离 线程间数据共享
Volitale
volatile是 Java 中用于修饰变量的关键字,主要解决多线程环境下的内存可见性和指令重排序问题。
核心特性:
- 可见性:保证变量修改后立即对其他线程可见
- 有序性:禁止指令重排序优化
2. 底层原理
- 内存屏障(Memory Barrier):
- 写操作:强制将工作内存中的修改刷新到主内存
- 读操作:强制从主内存重新加载变量值
- JMM(Java Memory Model):遵循 happens-before 规则,确保多线程操作的顺序性
3. 适用场景
场景 说明 状态标志位 如 while (!stop)循环退出控制单例模式(DCL) 双重检查锁定中修饰实例变量(防止指令重排序) 多线程共享变量 简单共享变量(不保证原子性,如 i++仍需同步)4. 与
synchronized对比
特性 volatilesynchronized原子性 不保证(如 i++)保证 阻塞 非阻塞 阻塞 性能 更高(无锁) 较低(上下文切换开销) 作用范围 仅修饰变量 修饰方法/代码块 5. 代码示例
public class VolatileDemo { private volatile boolean flag = false; public void start() { new Thread(() -> { while (!flag) { // 循环直到 flag 变为 true } System.out.println("Thread stopped by flag"); }).start(); new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; // 修改 flag 值 System.out.println("Flag set to true"); }).start(); } public static void main(String[] args) { new VolatileDemo().start(); } }输出:
Flag set to true
Thread stopped by flag6. 注意事项
- 不保证原子性:复合操作(如
count++)仍需使用synchronized或AtomicInteger- 性能影响:频繁读写
volatile变量会强制内存同步,可能降低性能- 替代方案:
- 原子类(
AtomicInteger等)final不可变对象7. 常见误区
- 误区1:认为
volatile能替代锁(实际仅解决可见性和有序性)- 误区2:滥用
volatile导致伪共享(False Sharing)问题8. 扩展:DCL 单例模式
public class Singleton { private static volatile Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // 防止指令重排序 } } } return instance; } }
线程池的概念
线程池是一种多线程处理形式,它预先创建一组线程并管理它们的生命周期,避免了频繁创建和销毁线程的开销。线程池的核心思想是线程复用,通过维护一个线程队列来执行多个任务。
线程池分类
java.util.concurrent包下的Excutor类创建线程池
Executors.newSingleThreadExecutor();
Executors.newScheduledThreadPool(int corePoolSize);
Executors.newCachedThreadPool();
Executors.newFixedThreadPool(int nThreads);
Spring的封装线程池
@Bean(name = "") public ThreadPoolTaskExecutor readPoolTaskExecutor() { ThreadPoolTaskExecutor billDeductExecutor = new ThreadPoolTaskExecutor(); //核心线程数目 ,cpu个数 * 2 billDeductExecutor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2); //指定最大线程数 billDeductExecutor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2); //队列中最大的数目 billDeductExecutor.setQueueCapacity(128); //线程空闲后的最大存活时间,单位:s billDeductExecutor.setKeepAliveSeconds(60); // 线程池对拒绝任务(无线程可用)的处理策略 billDeductExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //线程名称前缀 billDeductExecutor.setThreadNamePrefix("OperatorBillThreadPool-"); return billDeductExecutor; }定时任务线程池(ScheduledThreadPool)
/** * 执行周期性或定时任务 */ @Bean(name = "scheduledExecutorService") protected ScheduledExecutorService scheduledExecutorService() { return new ScheduledThreadPoolExecutor(corePoolSize, new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build()) { @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); Threads.printException(r, t); } }; } /** * 任务是否执行心跳维持任务 * * @param operatorCode 运营商编码 * @return 任务 */ private ScheduledFuture<?> keepAlive(String operatorCode) { String hbKey = OPERATOR_STATE_COMPARE_KEY + operatorCode + ":hearBeat"; return scheduledExecutorService.scheduleAtFixedRate(() -> { log.error("OrderOperatorStaCmpTask|operatorCode={}|keepAlive", operatorCode); redisCache.setCacheObject(hbKey, "1", 32L, TimeUnit.SECONDS); }, 0L, 30L, TimeUnit.SECONDS); }分治任务计算线程池
@Bean(name = "billCalculateThreadPool") public ForkJoinPool billCalculateThreadPool() { return new ForkJoinPool(10); }ForkJoinPool的核心特点
ForkJoinPool是Java 7引入的一种特殊线程池,专为分治算法和递归任务设计,采用工作窃取(work-stealing)算法实现高效并行计算。
与普通线程池(ThreadPoolExecutor)的主要区别
特性 ForkJoinPool ThreadPoolExecutor 设计目标 处理可分解的并行任务 处理独立任务 任务队列 每个线程有自己的双端队列 共享的任务队列 任务调度 工作窃取算法 先进先出(FIFO) 任务类型 ForkJoinTask(RecursiveAction/RecursiveTask) Runnable/Callable 适用场景 递归/分治任务(如归并排序) 常规异步任务 ForkJoinPool的优势
工作窃取算法
每个线程维护自己的任务队列
空闲线程可以从其他线程队列"窃取"任务
减少线程竞争,提高CPU利用率
更适合递归任务
自动处理任务分解和结果合并
简化分治算法的并行实现
更优的负载均衡
动态平衡各线程的工作量
避免某些线程空闲而其他线程过载
减少同步开销
本地队列操作减少锁竞争
适合计算密集型任务
ForkJoinPool的局限性
不适合I/O密集型任务
设计初衷是CPU密集型计算
线程阻塞会降低工作窃取效率
任务分解需要成本
小任务分解可能得不偿失
需要合理设置阈值
调试复杂度高
递归/并行执行路径难以跟踪
异常处理更复杂
内存消耗较大
每个线程维护独立队列
任务对象较多时占用更多内存
使用场景建议
适合使用ForkJoinPool的情况:
递归算法(如快速排序、归并排序)
可分解的大规模计算任务
需要自动任务分解/结果合并的场景
CPU密集型并行计算
适合使用ThreadPoolExecutor的情况:
独立任务处理(如Web请求)
I/O密集型操作
不需要任务分解的常规异步任务
需要更精细控制线程行为的场景
性能对比示例
对于计算斐波那契数列:
ForkJoinPool在n>30时优势明显
ThreadPoolExecutor在小规模计算(n<20)时更高效
对于文件处理:
ThreadPoolExecutor在I/O操作上表现更好
ForkJoinPool可能因线程阻塞而性能下降
自定义线程池创建
更灵活的方式是直接使用
ThreadPoolExecutor构造函数:ThreadPoolExecutor executor = new ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler );参数详解
corePoolSize (核心线程数)
- 线程池中保持的最小线程数
- 即使线程空闲也不会被回收(除非设置allowCoreThreadTimeOut)
maximumPoolSize (最大线程数)
- 线程池允许的最大线程数
- 当工作队列满时,会创建新线程直到达到此值
keepAliveTime (线程空闲时间)
- 非核心线程的空闲存活时间
- 超过此时间且线程数大于corePoolSize时,线程会被回收
unit (时间单位)
- keepAliveTime的时间单位(TimeUnit.SECONDS等)
workQueue (工作队列)
- 保存待执行任务的阻塞队列
- 常见实现:
- ArrayBlockingQueue: 有界队列
- LinkedBlockingQueue: 无界队列
- SynchronousQueue: 不存储元素的队列
threadFactory (线程工厂)
- 用于创建新线程
- 可以自定义线程名称、优先级等
rejectedExecutionHandler (拒绝策略)
- 当线程池和工作队列都满时的处理策略
- 内置策略:
- AbortPolicy: 抛出RejectedExecutionException(默认)
- CallerRunsPolicy: 由调用线程执行该任务
- DiscardPolicy: 直接丢弃任务
- DiscardOldestPolicy: 丢弃队列中最老的任务
线程池工作流程
- 提交任务时,如果当前线程数 < corePoolSize,创建新线程执行任务
- 如果线程数 ≥ corePoolSize,将任务放入工作队列
- 如果队列已满且线程数 < maximumPoolSize,创建新线程执行任务
- 如果队列已满且线程数 = maximumPoolSize,执行拒绝策略
最佳实践
- 根据任务类型选择合适线程池
- 合理设置核心和最大线程数
- 选择合适的队列容量
- 为线程池设置有意义的名称(通过ThreadFactory)
- 考虑使用自定义拒绝策略
- 监控线程池运行状态
线程池应用队列
1. ThreadPoolExecutor 队列选择
队列类型 特点 适用场景 LinkedBlockingQueue 无界队列(默认),任务积压可能导致OOM 任务量可控的短时异步任务 ArrayBlockingQueue 有界队列,固定容量,队列满时触发拒绝策略 需要限制资源消耗的稳定流量场景 SynchronousQueue 直接传递队列,无缓冲,任务立即交给空闲线程或新建线程 高吞吐量、瞬时高并发任务 PriorityBlockingQueue 优先级队列,任务按优先级排序 需要任务优先级调度的场景 推荐组合:
newFixedThreadPool+LinkedBlockingQueue(默认)newCachedThreadPool+SynchronousQueue(默认)
2. ScheduledThreadPoolExecutor 队列
队列类型 特点 必要性 DelayedWorkQueue 内部专用延迟队列,按任务触发时间排序 必须使用(强制内置实现) 注意:无法自定义队列,所有定时任务均依赖此队列。
3. ForkJoinPool 队列
队列类型 特点 必要性 Work-Stealing队列 每个线程维护双端队列,空闲线程可窃取其他队列任务 必须使用(强制内置实现) 优势:自动负载均衡,适合递归分治任务。
4. 特殊场景队列选择
场景 推荐队列 原因 高吞吐+低延迟 SynchronousQueue避免任务排队,直接分配线程 资源严格限制 ArrayBlockingQueue+ 拒绝策略防止任务无限堆积导致OOM 任务优先级差异大 PriorityBlockingQueue确保高优先级任务优先执行 批量任务处理 LinkedBlockingQueue缓冲任务,平衡生产者和消费者速度
5. 拒绝策略搭配建议
选择队列时需同步考虑拒绝策略(如
AbortPolicy、CallerRunsPolicy等),例如:
-
ArrayBlockingQueue +AbortPolicy:快速失败,保护系统稳定性-
LinkedBlockingQueue +CallerRunsPolicy:降级为同步执行,避免丢失任务
总结
- 常规任务:优先选
LinkedBlockingQueue(平衡性最佳)- 定时任务:强制使用
DelayedWorkQueue- 分治任务:强制使用
Work-Stealing队列- 极端场景:根据吞吐量/资源限制选择
SynchronousQueue或ArrayBlockingQueue合理搭配队列与线程池参数(核心线程数、最大线程数等)才能最大化性能。
锁的分类
1. 按线程竞争策略分类
类型 特点 适用场景 悲观锁 默认会发生冲突,操作前先加锁(如 synchronized,ReentrantLock)写多读少、冲突概率高的场景 乐观锁 假设不会冲突,提交时检查版本(如 CAS,AtomicInteger)读多写少、冲突概率低的场景
2. 按资源共享策略分类
类型 特点 示例 独占锁(排他锁) 同一时刻只允许一个线程访问(如 ReentrantLock)数据库行锁、文件写入锁 共享锁 允许多个线程同时读取(如 ReentrantReadWriteLock.ReadLock)数据库表级锁、缓存读锁
3. 按锁的实现方式分类
类型 实现原理 内置锁 JVM 原生支持( synchronized关键字)显式锁 JDK 提供的锁接口( Lock接口,如ReentrantLock)分布式锁 跨进程协调(如 Redis 的 SETNX、ZooKeeper 临时节点)自旋锁 线程循环尝试获取锁(如 AtomicBoolean的compareAndSet)
4. 按锁的公平性分类
类型 特点 示例 公平锁 按请求顺序分配锁(防止线程饥饿) new ReentrantLock(true)非公平锁 允许插队(性能更高,但可能造成饥饿) synchronized、ReentrantLock()
5. 按锁的重入性分类
类型 特点 示例 可重入锁 同一线程可重复获取锁(防止死锁) synchronized、ReentrantLock不可重入锁 线程重复获取会阻塞自身(易死锁) 自定义简单锁
6. 按锁的粒度分类
类型 特点 示例 偏向锁 无竞争时消除同步开销(JVM 优化) JDK6+ 默认开启 轻量级锁 短时竞争时用CAS替代阻塞 JVM 自动升级 重量级锁 线程竞争激烈时,通过操作系统互斥实现 synchronized最终状态
7. 特殊锁类型
类型 特点 使用场景 分段锁 将数据分段加锁(如 ConcurrentHashMap)高并发集合类 读写锁 读共享,写互斥(如 ReentrantReadWriteLock)读多写少场景(缓存) 邮戳锁 乐观读锁(StampedLock) 极高性能读场景 条件锁 基于条件阻塞(如 Condition.await())生产者消费者模型
8. 锁的升级过程(JVM 优化)
mermaidCopy Code
graph LR A[无锁] -->|首次访问| B[偏向锁] B -->|发生竞争| C[轻量级锁] C -->|竞争加剧| D[重量级锁]
9. 选锁黄金法则
- 简单场景 → 优先用
synchronized(自动释放、JVM 优化)- 高性能需求 → 考虑
ReentrantLock + CAS(可控性强)- 读多写少 → 选择
ReadWriteLock或StampedLock- 分布式环境 → Redis/ZooKeeper 分布式锁
- 锁细化 → 分段锁或并发集合类
💡 锁性能口诀:
无锁 > 偏向锁 > 轻量级锁 > 重量级锁
乐观锁 > 悲观锁
读锁 > 写锁
10. 经典锁对比表
特性 synchronizedReentrantLockReadWriteLock锁获取方式 JVM 隐式获取 代码显式控制 显式控制读/写锁 可中断 ❌ ✅ ( lockInterruptibly)✅ 公平锁 ❌ ✅ (可配置) ✅ (可配置) 条件等待 单一 wait()/notify()多 Condition支持 性能 JDK6+ 大幅优化 高竞争下更优 读多写少场景最优 锁释放 自动释放 必须手动 unlock()手动释放


被折叠的 条评论
为什么被折叠?



