当前位置: 首页 > news >正文

Java锁机制知识点

一、锁的基础概念

1.1 什么是锁

在并发编程中,锁是用于控制多个线程对共享资源进行访问的机制。锁可以保证在同一时刻最多只有一个线程访问共享资源,从而保证数据的一致性。

1.2 锁的分类
  • 可重入锁 vs 不可重入锁:可重入锁允许同一个线程多次获得同一把锁,如synchronizedReentrantLock;不可重入锁要求线程在释放锁后才能再次获得锁。
  • 公平锁 vs 非公平锁:公平锁保证线程按照申请锁的顺序依次获得锁,类似于“先来先得”,如ReentrantLock在构造时指定true可实现公平锁;非公平锁不保证线程获取锁的顺序,可能会出现线程插队现象,ReentrantLock默认实现为非公平锁。
  • 独占锁 vs 共享锁:独占锁保证在同一时刻,只有一个线程可以获得锁,如写锁;共享锁允许多个线程同时获得锁,如读写锁中的读锁。
  • 乐观锁 vs 悲观锁:悲观锁假设线程间会发生资源争用,在访问共享资源前先加锁,如synchronizedReentrantLock;乐观锁假设线程间不会发生冲突,不加锁,访问数据时判断是否发生了冲突,若冲突则重试,如CAS(Compare-And-Swap)。
  • 偏向锁、轻量级锁、重量级锁:这三种锁特指synchronized锁的状态,它们通过JVM内部的对象头(Mark Word)来控制锁的状态。偏向锁适用于线程没有竞争的场景;轻量级锁通过CAS操作来避免线程阻塞,适合多线程之间竞争较少的场景;重量级锁通过操作系统的同步机制来实现,线程获取不到锁时会被阻塞,适合竞争激烈且锁持有时间较长的场景。锁的升级路径为:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。

二、常见锁机制详解

2.1 synchronized关键字
  • 基本使用
//修饰实例方法
public class SynchronizedExample {public synchronized void instanceMethod() {// 方法体}
}
//修饰静态方法
public class SynchronizedExample {public static synchronized void staticMethod() {// 方法体}
}
//修饰代码块
public class SynchronizedExample {public void blockMethod() {synchronized (this) {// 同步代码块}}
}
  • 实现原理synchronized的实现原理主要包括对象头中的Mark Word、monitor entermonitor exit,以及锁的升级过程(无锁 → 偏向锁 → 轻量级锁 → 重量级锁)。
  • 特性与缺点
    • 隐式锁:由JVM自动管理锁的获取与释放,程序员不需要手动操作。
    • 阻塞性:线程在获取不到锁时会被阻塞,直到锁被释放。
    • 不可中断:线程一旦获得锁就会一直持有,直到执行完同步代码块或方法才会释放锁。
    • 性能问题:由于是阻塞锁,在高并发环境下可能会引起较大的性能损失,尤其是在锁竞争激烈时。
  • 适用场景:适用于简单的同步场景,当需要同步的代码块较小,且竞争不激烈时;也适合锁粒度较粗的同步需求。
2.2 ReentrantLock
  • 基本使用
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {private final ReentrantLock lock = new ReentrantLock();private int count = 0;public void increment() {lock.lock(); // 获取锁try {count++;} finally {lock.unlock(); // 释放锁}}// 支持超时的获取锁方式public boolean tryIncrement() {if (lock.tryLock()) { // 尝试获取锁try {count++;return true;} finally {lock.unlock();}}return false;}
}
  • 高级特性
    • 可中断锁lock.lockInterruptibly()方法允许线程在等待获取锁时响应中断。
    • 公平锁:可以创建公平锁,确保按照请求锁的顺序进行,等待时间最长的线程先获得锁。
    • 条件变量:提供Condition接口,用于线程间的协调。
  • 特性与优缺点
    • 优点:支持可中断的锁等待、锁超时、非阻塞获取锁;可以创建公平锁;提供了更多的控制选项。
    • 缺点:需要手动获取和释放锁,代码容易出错。
  • 适用场景:需要可中断锁的场景;需要公平锁的场景;需要尝试锁的场景。
2.3 ReadWriteLock
  • 概述ReadWriteLock提供了读写分离的锁机制,允许多个线程同时读取资源,但在写入时只能有一个线程获取写锁。其实现类通常是ReentrantReadWriteLock
  • 工作原理
    • 读锁:多个读线程可以同时获取,读锁不会阻塞其他读线程。
    • 写锁:写锁是独占的,写锁获取时会阻塞所有读线程和写线程。
  • 特性与优缺点
    • 优点:在读多写少的场景下,能够极大地提高并发性能。
    • 缺点:如果写操作较为频繁,性能提升不明显;可能会出现写饥饿问题。
  • 适用场景:适用于读多写少的场景,如缓存实现。
2.4 StampedLock
  • 核心特性
    • 乐观读:通过tryOptimisticRead()实现无锁读取,校验数据版本(邮戳)。若校验失败(期间有写操作),再升级为悲观读锁。
    • 三种模式:写锁、悲观读锁、乐观读。
  • 代码示例
import java.util.concurrent.locks.StampedLock;public class StampedLockExample {private final StampedLock stampedLock = new StampedLock();// 乐观读public void optimisticRead() {long stamp = stampedLock.tryOptimisticRead();// 读取数据if (!stampedLock.validate(stamp)) {stamp = stampedLock.readLock();try {// 重新读取数据} finally {stampedLock.unlockRead(stamp);}}}// 写操作public void write() {long writeStamp = stampedLock.writeLock();try {// 修改数据} finally {stampedLock.unlockWrite(writeStamp);}}
}
  • 缺点:不可重入,同一线程重复获取锁会导致死锁;API复杂,需手动处理锁升级和邮戳验证。
  • 适用场景:需要极高读并发且写冲突少的场景,如实时数据分析。

三、锁的优化技术

3.1 锁粗化

将多个紧邻的小范围加锁操作合并为一次较大的加锁操作,从而减少锁的频繁获取和释放,降低锁的开销。例如:

// 优化前
for (int i = 0; i < 100; i++) {synchronized (lock) {// 执行一些操作}
}// 优化后
synchronized (lock) {for (int i = 0; i < 100; i++) {// 执行一些操作}
}
3.2 锁消除

JVM在JIT编译时,通过逃逸分析判断加锁的对象是否只在当前线程内使用,如果确定不会发生线程竞争,JVM会自动将这些锁消除,从而避免不必要的锁操作。例如:

public String concatenate(String s1, String s2) {StringBuffer sb = new StringBuffer();sb.append(s1);sb.append(s2);return sb.toString();
}

在上述代码中,StringBuffer对象只在方法内部使用,不会被其他线程访问,因此JVM可以消除锁操作。

3.3 偏向锁

偏向于第一个获取它的线程,如果该线程再次进入同步块,锁不会进行竞争,直接获取锁。这种优化适用于锁竞争较少的场景,可以减少加锁的开销。

3.4 轻量级锁

核心思想是避免线程阻塞,它通过CAS操作来获取锁,从而避免了线程的上下文切换。如果线程竞争激烈,轻量级锁会升级为重量级锁。

3.5 自旋锁与自适应自旋

自旋锁是指当线程尝试获取锁而失败时,不会立即进入阻塞状态,而是进行短暂的忙等待(自旋),等待锁的释放后再尝试获取。Java中使用了自适应自旋技术,会根据前一次自旋的结果动态调整自旋次数。

四、死锁问题

4.1 死锁产生的条件
  • 互斥使用:资源一次只能被一个线程独占使用。
  • 不可抢占:资源请求者不能强制从资源占有者手中抢夺资源,资源只能由占有者主动释放。
  • 请求和保持:当资源请求者在请求其他资源的同时保持对原因资源的占有。
  • 循环等待:多个线程存在环路的锁依赖关系而永远等待下去。
4.2 死锁的预防方法
  • 破坏“循环等待”条件 - 锁顺序化:强制所有线程以全局一致的固定顺序获取锁。
  • 避免持有并等待:线程在获取所有需要的资源之前,不占用任何资源。
  • 允许抢占:允许线程在必要时抢占其他线程的资源。
http://www.lryc.cn/news/576648.html

相关文章:

  • Java安装与使用教程
  • FPGA设计的上板调试
  • zookeeper Curator(2):Curator的节点操作
  • 移动端日志平台EMAS
  • 在C++中#pragma“可选预处理指令的作用“。
  • OpenCV图像噪点消除五大滤波方法
  • springboot+Vue逍遥大药房管理系统
  • Redis—主从复制
  • 多径信道下移动通信信号均衡技术研究与实现
  • 常用工具库
  • 领域驱动设计(DDD)【22】之限定建模技术
  • electron中显示echarts
  • 顺序表应用实践:从通讯录实现到性能优化深度解析
  • 第6篇:中间件——Gin的请求处理管道
  • 印度和澳洲的地理因素
  • c++ 学习(二、结构体)
  • WordPress最新版6.8.1安装教程
  • 如何修改discuz文章标题字数限制 修改成255
  • SQL关键字三分钟入门:ROW_NUMBER() —— 窗口函数为每一行编号
  • 力扣 刷题(第七十一天)
  • 车载诊断架构 --- 非易失性存储器(NVM)相关设置项
  • 电子电气架构 --- 车辆产品的生产周期和研发周
  • vue-29(创建 Nuxt.js 项目)
  • EXISTS 和 NOT EXISTS 、IN (和 NOT IN)
  • 基于Spring Boot的网上购物平台设计与实现
  • 星际争霸数据集指南
  • 桌面小屏幕实战课程:DesktopScreen 16 HTTP
  • MySQL 索引 -- 磁盘,主键索引,唯一索引,普通索引,全文索引
  • TDengine 如何使用 MQTT 采集数据?
  • PyQtNode Editor 第三篇创建节点(节点的定义)