Java高并发编程中的锁级别详解:从阻塞到无等待
本文深入解析Java高并发中的五种调度级别:阻塞、无饥饿、无障碍、无锁和无等待。通过分析线程在临界区的执行状态及冲突处理机制,帮助开发人员在悲观保护与乐观尝试之间做出选择,优化系统并发性能。
阻塞 (Blocking)
阻塞是最基础的线程调度方式。当多个线程竞争同一资源时,获取不到锁的线程会被操作系统挂起(挂起等待),直到持有资源的线程释放锁后才会被重新唤醒。这种机制依赖于底层操作系统的互斥锁(Mutex)或信号量实现。
在Java中,synchronized关键字和基于AQS实现的ReentrantLock均属于阻塞式同步。其特点是:
1. 线程安全性高,通过排他锁保证同一时刻只有一个线程执行临界区代码。
2. 存在上下文切换开销,线程挂起和恢复会消耗CPU时间。
无饥饿 (Starvation-Free)
无饥饿是指系统中的每一个线程最终都能获得运行机会,不会被永远排除在外。这主要取决于锁的公平性策略:
- 非公平锁:允许新到的线程插队。效率高,但可能导致低优先级或长等待时间的线程产生饥饿(Starvation)。
- 公平锁:严格按照先来后到(FIFO)的顺序分配资源。虽然牺牲了一定的吞吐量,但保证了所有线程执行的公平性,从根本上杜绝了饥饿。
无障碍 (Obstruction-Free)
无障碍是一种最弱的非阻塞调度策略。它允许线程自由进入临界区读取或修改共享数据,不进行排他锁定。如果发生数据竞争(即多个线程同时修改同一变量导致不一致),线程检测到冲突后会立即回滚自己的修改,并重新启动该操作。
这是一种“乐观”的并发策略:假设冲突概率较小,因此放弃保护机制以换取更高效率。但如果临界区竞争激烈,所有线程可能因频繁冲突而不断重试,导致程序无法推进。
典型实现案例:数据库乐观锁
利用版本号机制控制并发更新。示例流程如下:
// 1. 查询数据,记录当前版本号 v_version
// 2. 执行业务逻辑
// 3. 更新语句中包含版本判断,仅当版本号未变更时才成功 update
UPDATE table SET version = version + 1 WHERE id = ? AND version = v_version;
// 4. 若受影响行数 > 0,说明无冲突,提交;否则回滚并重试。
无锁 (Lock-Free)
无锁是比无障碍更高级别的并发协议。它同样不使用传统锁,但保证至少有一个线程能在有限步骤内完成操作并退出临界区。
在无锁编程中,通常依赖硬件提供的原子指令(如Compare-and-Swap, CAS)。线程会不断尝试修改共享变量,失败则重试。由于必然有线程获胜,系统不会出现所有线程都陷入死循环的情况,从而避免了完全的死锁或停滞。
Java中java.util.concurrent.atomic包下的原子类(如AtomicInteger)便是典型的无锁实现:
while (!atomicVar.compareAndSet(expectedValue, newValue)) {
expectedValue = atomicVar.get(); // 获取最新值继续重试
}
无等待 (Wait-Free)
无等待是无锁的进一步扩展,也是最强的非阻塞级别。它不仅要求系统整体进展,还规定每一个线程都必须在有限步骤内完成自己的操作,无论其他线程是否活跃或崩溃。这彻底消除了线程间相互影响的依赖,保证了绝对的响应速度。
常见实现:RCU (Read-Copy-Update) 技术
广泛用于Linux内核和高性能计算场景。核心思想是将读操作和写操作分离:
- 读端:直接访问内存指针,无需加锁,永远无等待。
- 写端:先创建数据的副本并修改副本,然后将全局指针原子地指向新副本,最后回收旧副本。这样既保证了读者的无等待特性,又实现了数据的更新。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









