AbstractQueuedSynchronizer详解
一、AQSAQS(AbstractQueuedSynchronizer,抽象队列同步器)是JUC(java.util.concurrent)包中构建锁和其他同步组件(如Semaphore, CountDownLatch, ReentrantReadWriteLock等)的基础框架。 你可以把它理解成一个多线程访问共享资源的“管理员”或“交警”。它的核心职责是:当共享资源被占用时,将未能成功获取资源的线程阻塞,并将其放入一个队列中进行管理;当资源被释放时,再从队列中唤醒等待的线程来尝试获取。 AQS的核心思想可以概括为三点: 一个状态量(State): 这是一个volatile int类型的变量,表示共享资源的状态。 不同的同步器对它的含义解读不同。对于ReentrantLock,state表示锁的重入次数(0表示未被占用,>0表示被占用且数值为重入次数);对于Semaphore,state表示剩余的许可证数量;对于CountDownLatch,state表示倒计数的数值。 一个FIFO线程等待队列(CLH变体): 这是一个双向链表结构的队列,用于存放所有等待获取资...
synchronized和ReentrantLock的区别
在 Java 并发编程中,synchronized 和 ReentrantLock 都是实现线程同步、保证互斥访问共享资源的关键机制,但它们在设计理念、功能和使用方式上存在显著差异。以下是主要区别: 一. 本质与实现方式 synchronized: Java 语言内置的关键字。 是 Java 语法的一部分。 隐式锁(监视器锁)。 基于 JVM 底层(对象头中的 Mark Word)的 monitorenter 和 monitorexit 指令实现锁的获取和释放。锁的获取和释放由 JVM 自动管理。 与对象关联。 锁是附加在对象上的(实例对象或 Class 对象)。 ReentrantLock: java.util.concurrent.locks 包中的一个类。 是 JDK 提供的 API。 显式锁。 开发者需要显式地调用 lock() 方法来获取锁,并(必须)在 finally 块中调用 unlock() 方法来释放锁。 独立的对象。 ReentrantLock 本身就是一个锁对象。 二. 锁的获取与释放 synchronized: 自动获取与释放。 线程进入 syn...
synchronized的偏向锁、轻量级锁、重量级锁
Java 中的 synchronized 关键字是实现线程同步的核心机制。为了在不同竞争程度的场景下优化性能,JVM 采用了锁升级策略。对象锁的状态会随着竞争情况的变化,从无锁状态逐步升级:偏向锁 -> 轻量级锁 -> 重量级锁。 这些锁的状态信息存储在 Java 对象头的 Mark Word 中。 偏向锁 (Biased Locking) 目标: 优化同一个线程多次获取同一把锁的场景(无实际竞争或极少竞争)。 原理: 当一个线程第一次获得锁时,JVM 会在对象头的 Mark Word 中记录该线程的 ID(偏向线程ID)以及一个偏向时间戳(epoch)。 之后该线程再次进入这个同步块时,不需要进行任何同步操作(如 CAS、系统调用),只需简单检查 Mark Word 中的线程 ID 是否指向自己: 如果是:直接执行。 如果不是:说明存在竞争(即使另一个线程已经释放了锁,偏向也不会自动撤销),此时需要撤销偏向锁。 撤销偏向锁: 需要等到全局安全点(此时 JVM 暂停所有工作线程)。 检查持有偏向锁的线程是否还活着或是否仍在同步块内。 如果线程已不存活或不在同...
Java线程池中任务未捕捉异常问题分析
当一个在 Java 线程池中执行的任务抛出了未捕获的异常时,会导致以下几个关键问题: 1. 任务执行线程的终止(最直接的影响)默认情况下,未捕获的异常会导致执行该任务的当前线程结束。 对于 ThreadPoolExecutor:线程池中的核心线程(corePoolSize)是宝贵的资源。如果一个核心线程因为任务异常而终止,线程池为了维持核心线程数,会创建一个新的线程来替代它。虽然线程池有自我修复能力,但这个创建和销毁的过程会有额外的开销。 对于 ForkJoinPool:行为类似,工作线程可能会被终止并可能被替换。 关键点:任务本身的失败不会直接影响线程池中其他线程的执行,但会导致执行它的那个特定线程结束。 2. 异常信息的丢失(非常危险!)这是最隐蔽和危险的问题。默认情况下,这个未捕获的异常只会被打印到标准错误流(System.err),而通常没有人会一直盯着控制台日志。 123456ExecutorService executor = Executors.newFixedThreadPool(2);executor.execute(() -> { t...
Java线程池动态调整队列容量
解决线程池中BlockingQueue无法动态调整大小的问题在Java线程池中,标准BlockingQueue实现(如ArrayBlockingQueue)创建后容量固定,无法动态调整。以下是完整的解决方案: 一、问题核心痛点 固定容量缺陷: 低负载时:队列过大浪费内存 高负载时:队列快速填满触发拒绝策略 业务场景需求: 流量波动大的系统(如电商秒杀) 云环境弹性伸缩(K8s HPA) 成本敏感型应用(按需分配资源) 二、解决方案:动态可调整队列方案1:自定义可调整阻塞队列1234567891011121314151617181920212223242526272829303132333435363738394041public class ResizableBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E> { private final ReentrantLock lock = new ReentrantLock(); ...
Java线程池为什么优先入队而非直接创建最大线程
Java线程池采用”核心线程→入队→非核心线程”的任务处理流程,这种设计背后有深刻的架构考量。 一、核心设计目标 资源节约:避免不必要的线程创建(线程创建成本≈1ms/次,消耗1MB+栈内存) 流量削峰:用队列缓冲突发流量,防止线程爆炸 稳定性优先:优先保证系统稳定,其次追求吞吐量 二、优先入队的本质原因 线程创建成本 远大于 入队成本 12345// 线程创建:需要系统调用、内存分配、上下文切换new Thread(() -> {...}).start(); // 成本高// 任务入队:只是内存操作queue.offer(task); // 成本极低 防止线程爆炸(Thread Thrashing) 如果直接创建最大线程: 突发1000个任务 → 立即创建1000个线程 系统资源可能被耗尽(内存/CPU上下文切换) 后续任务直接触发拒绝策略 优先入队方案: 突发1000任务–>核心线程处理N个–>队列缓冲M个–>非核心线程处理K个–>稳定后自动回收 符合典型业务场景 真实场景中: 70%的...
Java线程池底层工作原理
Java线程池底层工作原理Java线程池的核心实现位于java.util.concurrent.ThreadPoolExecutor类,其底层设计围绕任务调度、资源复用和流量控制展开。以下是关键机制的分步解析: 一、核心参数与状态管理线程池通过一个32位的ctl原子变量同时管理线程池状态和工作线程数量: 12private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));// 高3位:线程池状态,低29位:有效线程数 状态变迁: RUNNING:接受新任务,处理队列任务 SHUTDOWN:不接受新任务,处理队列任务 STOP:不接受新任务,不处理队列,中断进行中任务 TIDYING:所有任务终止,workerCount=0 TERMINATED:terminated()钩子执行完毕 二、任务执行流程(execute()方法) 核心线程处理 当前线程数 < corePoolSize → 创建新Worker(立即执行任务) 1addWorker(command, true...
java线程死锁
在Java中,死锁(Deadlock)是指两个或多个线程在执行过程中,因争夺资源而陷入互相等待的僵局。当以下四个必要条件同时满足时,就会发生死锁: 死锁产生的必要条件 互斥条件(Mutual Exclusion)资源一次只能被一个线程占用(例如:synchronized锁、数据库连接等独占资源)。 请求与保持(Hold and Wait)线程持有至少一个资源,同时请求其他线程占有的资源(不释放已持有资源)。 不可剥夺(No Preemption)线程已获得的资源在未使用完前,不能被强行剥夺(只能主动释放)。 循环等待(Circular Wait)存在线程资源的环形等待链:线程A → 等待 线程B 的资源 → 线程B → 等待 线程A 的资源。 死锁示例代码12345678910111213141516171819202122232425262728293031public class DeadlockDemo { private static final Object lock1 = new Object(); private static f...
Java线程安全
在 Java 中,线程安全是一个核心概念,指的是当多个线程并发访问某个类、对象或方法时,这个类、对象或方法始终能表现出正确的行为,而无需额外的同步或协调机制。 核心问题:共享数据的并发访问 线程不安全的根源在于多个线程同时读写共享的可变状态(数据)。 如果数据是只读的(不可变),或者每个线程操作的都是自己私有的数据副本,那么自然就是线程安全的。 问题在于:当多个线程尝试同时修改同一个变量、同一个集合、同一个文件等共享资源时,如果访问操作不是原子的,或者线程间的修改对其他线程不可见,就会导致数据不一致、逻辑错误、程序崩溃等不可预测的后果。 导致线程不安全的三大根源 原子性缺失: 一个操作(可能由多条字节码或机器指令组成)需要作为一个不可分割的整体执行。如果执行到一半被其他线程打断,就可能导致中间状态被其他线程观察到或修改,从而破坏一致性。 经典例子:i++。 它实际上包含读取i的值、将值加1、将结果写回i三个步骤。两个线程同时执行i++可能导致只增加一次。 可见性问题: 一个线程对共享变量的修改,不能立即被其他线程看到。这主要是由现代计算机架构(多级缓存)和 Java 内...
JVM垃圾回收算法
JVM 中的垃圾回收算法是其自动内存管理的核心,随着 JVM 的发展,出现了多种算法,各有优缺点和适用场景。主要可以分为以下几类: 基础算法 (通常作为其他更复杂算法的基础组件) 标记-清除算法: 过程: 分为两个阶段。 标记: 从 GC Roots(如活动线程栈帧中的引用、静态变量、JNI 引用等)出发,遍历对象图,标记所有可达对象为“存活”。 清除: 遍历整个堆,回收所有未被标记为“存活”的对象所占用的内存空间。 优点: 实现相对简单。 缺点: 效率问题: 标记和清除两个过程的效率都不算高(需要遍历整个堆)。 空间问题: 回收后会产生大量不连续的内存碎片。这会导致以后需要分配较大对象时,无法找到足够的连续内存,从而触发另一次 GC。 标记-复制算法: 过程: 将可用内存划分为容量相等的两块(如 From 和 To)。只使用其中一块(From)。当这块内存用尽时,就将还存活的对象复制到另一块内存(To)上,然后一次性清理掉已使用的内存块(From)的全部空间。之后交换 From 和 To 的角色。 优点: 实现简单,运行高效。 解决了内存碎片化问题(复制后存活对象...