尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Java 锁学习笔记
Java 锁学习笔记1. 为什么需要锁1.1 多线程为什么会出问题多个线程同时操作同一份共享数据时可能产生数据竞争Race Condition。例如privateintcount0;publicvoidadd(){count;}count看起来只有一行但实际上可以理解成读取 count ↓ count 1 ↓ 写回 count假设当前count 0两个线程同时执行线程 A读取 0 线程 B读取 0 线程 A0 1 → 写入 1 线程 B0 1 → 写入 1最终可能得到count 1但实际上执行了两次add()期望结果应该是2。这就是并发情况下的数据竞争。1.2 锁解决什么问题锁的核心作用让多个线程访问共享资源时按照一定规则进行协调。最简单的理解多个线程 ↓ 竞争同一把锁 ↓ 同一时间只能一个线程进入 ↓ 其他线程等待注意锁不是让整个 Java 程序变成单线程而是让竞争同一把锁的线程互斥。2. synchronized —— 同步锁2.1 什么是 synchronizedsynchronized是 Java 提供的同步机制。最简单理解同一把锁同一时间只能有一个线程进入受保护的代码。例如privateintcount0;publicsynchronizedvoidadd(){count;}多个线程同时调用时线程 A → 获得锁 → 执行 add() 线程 B → 等待 线程 C → 等待 A 执行结束 → 释放锁 B → 获得锁 → 执行2.2 synchronized 锁住的到底是什么这是理解synchronized最重要的一点synchronized 锁的是对象而不是代码本身。synchronized 实例方法publicsynchronizedvoidadd(){count;}可以近似理解为publicvoidadd(){synchronized(this){count;}}也就是说实例方法锁住的是当前对象this。synchronized 代码块privatefinalObjectlocknewObject();publicvoidadd(){synchronized(lock){count;}}这里锁的是lock对象。static synchronizedpublicstaticsynchronizedvoidtest(){}锁的是MyService.class2.3 什么情况下两个线程会互斥关键不是“是不是 synchronized 方法”而是是不是竞争同一把锁例如publicsynchronizedvoidadd(){}publicsynchronizedvoidsubtract(){}如果两个方法属于同一个对象那么add() → 锁 this subtract() → 锁 this所以它们会互斥。即使是两个不同的方法也需要排队。2.4 不同对象的 synchronized 不会互相阻塞例如MyServiceserviceAnewMyService();MyServiceserviceBnewMyService();此时serviceA.add() ↓ 锁 serviceA serviceB.add() ↓ 锁 serviceB两把锁不同所以可以同时执行。因此同一把锁 → 互斥 不同的锁 → 可以并发2.5 synchronized 的可重入synchronized本身就是可重入的。例如publicsynchronizedvoidmethodA(){methodB();}publicsynchronizedvoidmethodB(){}执行过程线程 A ↓ 进入 methodA() ↓ 获得 this 的锁 ↓ 调用 methodB() ↓ 再次获得 this 的锁 ↓ 正常执行不会把自己锁死。所以可重入 同一个线程已经持有一把锁时可以再次获得同一把锁。注意可重入不代表多个线程可以同时执行。2.6 synchronized 常见错误不要每次创建新的锁对象synchronized(newObject()){// ...}因为每次调用都会创建不同的对象线程之间没有竞争同一把锁。应该使用共享的锁对象privatefinalObjectlocknewObject();publicvoidtest(){synchronized(lock){// ...}}3. ReentrantLock —— 可重入锁3.1 什么是 ReentrantLockReentrantLock中文可重入锁它是java.util.concurrent.locks包中的一个具体锁实现。privatefinalReentrantLocklocknewReentrantLock();3.2 最基本的使用方式privatefinalReentrantLocklocknewReentrantLock();publicvoidadd(){lock.lock();try{count;}finally{lock.unlock();}}执行流程lock() ↓ 获取锁 ↓ 执行代码 ↓ unlock() ↓ 释放锁3.3 为什么必须 finally unlock因为ReentrantLock是手动管理锁。标准写法lock.lock();try{// 业务}finally{lock.unlock();}如果业务代码抛出异常而没有finally可能导致锁无法正常释放。3.4 synchronized 和 ReentrantLock 的区别synchronizedsynchronized(lock){// ...}Java 自动管理进入代码块 → 自动获得锁 离开代码块 → 自动释放锁ReentrantLocklock.lock();try{// ...}finally{lock.unlock();}程序员自己控制lock() ↓ 获得锁 unlock() ↓ 释放锁所以可以简单理解synchronized 更简单ReentrantLock 给你更多手动控制能力。3.5 tryLock()ReentrantLock一个重要能力lock.tryLock();意思是尝试获取锁拿不到就不一直等待。例如if(lock.tryLock()){try{// 执行任务}finally{lock.unlock();}}else{System.out.println(当前已经有人执行了);}区别lock() ↓ 拿不到 → 一直等 tryLock() ↓ 拿不到 → 返回 false还可以lock.tryLock(3,TimeUnit.SECONDS);表示最多等待 3 秒。3.6 ReentrantLock 也是可重入的同一个线程已经获得lock后可以再次获得同一把锁不会把自己锁死。4. ReentrantReadWriteLock —— 读写锁4.1 为什么需要读写锁假设 Java 内存中有一个共享缓存privateMapString,StringcachenewHashMap();大量线程读取cache.get(user);偶尔才修改cache.put(user,Tom);如果所有操作都使用普通互斥锁即使多个线程都只是读取也需要排队。读写锁就是解决这个问题的。4.2 基本写法通常这样定义privatefinalReadWriteLocklocknewReentrantReadWriteLock();这里ReadWriteLock ↓ 接口 ReentrantReadWriteLock ↓ 具体实现类似ListStringlistnewArrayList();4.3 读锁publicStringget(Stringkey){lock.readLock().lock();try{returncache.get(key);}finally{lock.readLock().unlock();}}多个线程可以同时获得读锁线程 A → 读锁 → 读取 线程 B → 读锁 → 读取 线程 C → 读锁 → 读取所以读 读可以并发。4.4 写锁publicvoidput(Stringkey,Stringvalue){lock.writeLock().lock();try{cache.put(key,value);}finally{lock.writeLock().unlock();}}写锁更加严格读 写 → 互斥 写 写 → 互斥总结读锁 写锁 读锁 ✅ ❌ 写锁 ❌ ❌4.5 ReentrantReadWriteLock 也是可重入的ReentrantReadWriteLock也是可重入锁。例如同一个线程持有写锁后可以再次获得同一个写锁。因此synchronized ReentrantLock ReentrantReadWriteLock都支持可重入。5. StampedLock —— 邮戳锁5.1 StampedLock 是什么StampedLock提供读锁 写锁 乐观读其中最重要的特色是乐观读Optimistic Read。5.2 什么是乐观读普通读锁我要读 ↓ 先获得读锁 ↓ 读取数据 ↓ 释放读锁乐观读我要读 ↓ 先不真正加读锁 ↓ 直接读取 ↓ 最后检查期间有没有发生写操作也就是我乐观地认为读取过程中不会有人修改数据。5.3 基本代码privateintx10;privateinty20;privatefinalStampedLocklocknewStampedLock();publicintgetTotal(){longstamplock.tryOptimisticRead();intcurrentXx;intcurrentYy;if(!lock.validate(stamp)){stamplock.readLock();try{currentXx;currentYy;}finally{lock.unlockRead(stamp);}}returncurrentXcurrentY;}不用死记代码重点理解流程开始读取 ↓ 乐观读取 ↓ 检查读取期间有没有写操作 ↓ 没有 → 读取结果有效 有 → 重新获得读锁 → 重新读取5.4 为什么叫“乐观”因为它采用的是一种乐观思想我认为冲突大概率不会发生所以先操作最后再检查。5.5 乐观读和乐观锁两者思想很接近乐观读 ↓ 先读 ↓ 最后验证 ↓ 发现冲突 → 重新读数据库中的乐观锁也常采用类似思路先读取数据 ↓ 记录 version ↓ 执行更新 ↓ 检查 version 是否变化 ↓ 没有变化 → 成功 发生变化 → 冲突因此乐观读采用了和乐观锁类似的并发控制思想但两者不是同一个机制。5.6 StampedLock 不可重入前面几个synchronized ReentrantLock ReentrantReadWriteLock都是可重入的。但是StampedLock 不支持可重入。这一点需要特别记住。6. 乐观锁 vs 悲观锁6.1 悲观锁核心思想我认为冲突很可能发生所以先把资源锁住再操作。流程先锁 ↓ 执行操作 ↓ 释放锁例如synchronized(lock){// 修改共享数据}线程线程 A → 拿到锁 → 执行 线程 B → 拿不到 → 等待这体现了典型的悲观思想“我认为别人可能会同时操作所以我先把门锁起来。”6.2 乐观锁核心思想我认为冲突不一定发生所以先操作最后检查有没有冲突。流程先操作 ↓ 最后检查 ↓ 没有冲突 → 成功 发生冲突 → 失败 / 重试 / 重新读取常见实现思路是版本号version 1更新时UPDATEaccountSETbalance900,versionversion1WHEREid1ANDversion1;如果其他线程已经把version 1改成version 2那么这次更新就不会成功。6.3 悲观锁 vs 乐观锁悲观锁乐观锁核心思想先锁住先操作冲突处理事前阻止事后检查冲突发生时等待失败 / 重试Java 中的典型体现synchronized、ReentrantLockCAS、版本号等思想数据库例子FOR UPDATEversion 字段6.4 “悲观锁”和“乐观锁”不是具体 Java 类这一点非常重要。Java 中没有PessimisticLock这样的标准锁类。“悲观锁”和“乐观锁”描述的是一种并发控制思想。例如synchronized ↓ Java 同步机制 ↓ 体现悲观并发控制思想ReentrantLock ↓ Java 可重入锁 ↓ 体现悲观并发控制思想而StampedLock 的乐观读 ↓ 体现乐观并发控制思想数据库也同样如此SELECT ... FOR UPDATE ↓ 悲观锁思想version / CAS ↓ 乐观锁思想
RELATED

相关推荐

向量数据库Milvus: 索引类型(三)

向量数据库Milvus: 索引类型(三)

一、为什么需要索引?没有索引时,Milvus 会做暴力搜索(FLAT):把查询向量和 Collection 里每一个向量都算一遍相似度,然后排序取 Top-K。这种方式精度 100%,但速度随数据量线性下降。100 万条数据…

📅 2026/9/24 17:40:29
说好底片全送精修30张,交片时只剩15张?用轻虾导出微信聊天记录留证,摄影约拍留痕攻略 + 6 大避坑

说好底片全送精修30张,交片时只剩15张?用轻虾导出微信聊天记录留证,摄影约拍留痕攻略 + 6 大避坑

约拍这件事,从询价到拿片少则两周、多则两三个月,中间几乎全靠微信推进:套系里含什么、底片给多少张、精修几张、加片怎么算钱、外景改期怎么定、什么时候交片。问题在于,这些约定往往散落在几十次对话里,有的在询价时…

📅 2026/9/24 17:40:29
全局可控与精细化分类:知源AI分级系统赋能教育数据治理落地方案

全局可控与精细化分类:知源AI分级系统赋能教育数据治理落地方案

一、方案概要:AI驱动教育数据精细化治理,实现全域可控与高效落地提示:本方案基于教育数据治理合规要求与业务痛点,依托AI技术构建全流程治理体系,实现数据管控、治理效率、分类精度的三维升级。随着教育数字化深度落地…

📅 2026/9/24 17:35:26
MORE NEWS

更多资讯

📰

Ubuntu系统运行AppImage全指南:从libfuse报错到高效使用

先别急着双击那个.AppImage文件,因为大概率你会看到两种情况:要么鼠标点了没反应,要么终端里甩给你一行error loading libfuse.so.2。这个格式在Ubuntu上跑起来确实有一点门槛,但背后的逻辑其实特别简单,搞懂之后&…

📰

零基础学网络安全:从加密、防火墙到日志分析的五大入门技术

1. 先说结论:网络安全没有想象中那么难,难的是没找对入门顺序很多人一听到"网络安全技术"这六个字,脑子里浮现的是黑色终端窗口、飞快滚动的代码、戴着兜帽的神秘人,于是下意识觉得自己学不了。我接触这个领域十几年&am…

📰

Ubuntu 运行 AppImage 指南:从权限到 FUSE 依赖一次讲透

遇到.AppImage双击没反应?这份 Ubuntu 运行指南请收好如果你最近刚从官网下载了一个 Linux 软件,发现它既不是.deb也不是.tar.gz,而是一个名字结尾是.AppImage的文件,双击之后却没有任何反应——别急着怀疑系统坏了。这可能是你第…

📰

承认平庸,是普通人回报率最高的思维转换

把“承认平庸”当成一笔生意来算,是我这几年做得最值的一次思维转换。过去我拼命想证明自己不普通,怕被贴上“平庸”的标签,怕落后、怕被淘汰,结果该熬的夜一天没少,该焦虑的事一件没躲。后来我试着承认自己就是平庸&a…

📰

百度网盘下载慢怎么办?2026年提速排障与替代方案全解析

1. 从一次绝望的下载说起:百度网盘为什么越下越慢如果你经历过下载一个 5GB 的镜像文件,百度网盘却以 80KB/s 的速度“龟速爬行”,预估剩余时间显示“1天3小时”,那我完全理解你点开这篇文章时的心情。2026 年了,百度网…

📰

DirectX修复工具详解:一键解决dll缺失与游戏闪退

最近一个朋友的笔记本老毛病又犯了,Win11系统,玩个老网游不到五分钟就闪退,弹窗提示“缺少d3dx9_42.dll”。这类错误听起来挺吓人,其实多数情况并不是显卡坏了,也不是游戏文件损坏,而是系统里的DirectX运行…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬