[{"data":1,"prerenderedAt":4048},["ShallowReactive",2],{"java:kafka-message-loss-prevention\u002F":3,"java-wiki-navigation":1017},{"id":4,"title":5,"body":6,"commentId":1004,"description":1005,"difficulty":1006,"draft":1007,"extension":1008,"meta":1009,"navigation":553,"order":1010,"path":1011,"section":1012,"seo":1013,"stem":1014,"updated":1015,"__hash__":1016},"java\u002Fjava\u002Fkafka-message-loss-prevention.md","Kafka 如何保证消息不丢失",{"type":7,"value":8,"toc":965},"minimark",[9,13,17,33,36,44,47,50,57,60,74,78,81,97,100,104,111,118,179,182,198,207,211,214,247,250,253,268,272,275,286,293,329,336,340,343,351,354,357,363,366,370,374,377,383,386,389,393,396,399,405,408,414,419,425,428,434,437,442,445,449,458,461,464,470,480,486,489,493,497,506,509,515,518,522,525,531,534,586,589,593,596,602,605,622,629,633,636,642,645,654,657,660,664,714,717,724,727,731,840,844,851,859,863,869,873,876,880,883,887,890,894,897,917,920,923,961],[10,11,5],"h1",{"id":12},"kafka-如何保证消息不丢失",[14,15,16],"h2",{"id":16},"面试回答",[18,19,20],"blockquote",{},[21,22,23,24,28,29,32],"p",{},"Kafka 保证消息不丢失需要覆盖生产者、Broker 和消费者三段链路。生产者侧使用 ",[25,26,27],"code",{},"acks=all","、开启重试和幂等生产，并检查发送回调，对最终失败进行落库、告警或补偿；Broker 侧通常设置三副本、",[25,30,31],{},"min.insync.replicas=2","，并禁止非同步副本参与 Leader 选举，保证消息在足够多的同步副本写入后才确认成功；消费者侧关闭自动提交，在业务处理成功后再提交 Offset，失败时不提交以便重新消费。这样通常实现至少一次投递，所以消费端还必须通过事件 ID、唯一索引或状态机保证幂等。涉及数据库与 Kafka 的一致性时，可以使用本地消息表或 Transactional Outbox。Kafka 的消息不丢失是端到端保障，不是只配置一个参数。",[21,34,35],{},"一句话总结：",[18,37,38],{},[21,39,40],{},[41,42,43],"strong",{},"生产者确认成功，Broker 多副本可靠保存，消费者处理成功后提交，业务端用幂等和补偿兜底。",[14,45,46],{"id":46},"详细讲解",[21,48,49],{},"Kafka 消息不丢失不是靠某一个参数实现的，而是一个端到端问题：",[18,51,52],{},[21,53,54],{},[41,55,56],{},"生产者必须确认消息发送成功，Broker 必须把消息可靠地保存在足够多的副本中，消费者必须在业务处理成功后再提交 Offset。",[21,58,59],{},"其中任何一个环节处理不当，都可能出现“业务上看起来消息丢了”的情况。",[21,61,62],{},[63,64,70],"a",{"href":65,"target":66,"rel":67},"\u002Fimages\u002Fwiki\u002Fjava\u002Fkafka-message-loss-prevention.svg","_blank",[68,69],"noopener","noreferrer",[71,72],"img",{"src":65,"alt":73},"Kafka 消息不丢失的端到端保障链路",[14,75,77],{"id":76},"一先明确什么叫消息丢失","一、先明确什么叫“消息丢失”",[21,79,80],{},"常见的丢失场景主要有四类：",[82,83,84,88,91,94],"ol",{},[85,86,87],"li",{},"生产者调用发送方法后没有检查结果，实际发送失败却被业务忽略。",[85,89,90],{},"Leader 收到消息后就返回成功，消息还未复制到 Follower，Leader 随后故障。",[85,92,93],{},"消费者先提交 Offset，再执行业务；业务处理失败后，消息不会再次投递。",[85,95,96],{},"Kafka 中的消息没有丢，但写数据库、调用下游等业务操作失败，又没有补偿机制。",[21,98,99],{},"因此，Kafka 中仍能查到消息，不代表业务一定处理成功；反过来，业务没有结果，也不一定是 Broker 丢了消息。",[14,101,103],{"id":102},"二生产者如何避免丢消息","二、生产者如何避免丢消息",[105,106,108,109],"h3",{"id":107},"_1-使用-acksall","1. 使用 ",[25,110,27],{},[21,112,113,114,117],{},"生产者的 ",[25,115,116],{},"acks"," 决定 Broker 在什么条件下确认发送成功：",[119,120,121,137],"table",{},[122,123,124],"thead",{},[125,126,127,131,134],"tr",{},[128,129,130],"th",{},"配置",[128,132,133],{},"确认时机",[128,135,136],{},"风险",[138,139,140,154,167],"tbody",{},[125,141,142,148,151],{},[143,144,145],"td",{},[25,146,147],{},"acks=0",[143,149,150],{},"不等待 Broker 响应",[143,152,153],{},"发送失败也无法感知，风险最高",[125,155,156,161,164],{},[143,157,158],{},[25,159,160],{},"acks=1",[143,162,163],{},"Leader 写入本地日志后返回",[143,165,166],{},"Leader 故障且副本尚未同步时可能丢失",[125,168,169,173,176],{},[143,170,171],{},[25,172,27],{},[143,174,175],{},"当前 ISR 中的副本都确认后返回",[143,177,178],{},"Kafka 能提供的最强生产者确认保证",[21,180,181],{},"关键配置：",[183,184,189],"pre",{"className":185,"code":186,"language":187,"meta":188,"style":188},"language-properties shiki shiki-themes github-light github-dark","acks=all\n","properties","",[25,190,191],{"__ignoreMap":188},[192,193,196],"span",{"class":194,"line":195},"line",1,[192,197,186],{},[21,199,200,202,203,206],{},[25,201,27],{}," 并不等于绝对不丢。它还需要与副本数和 ",[25,204,205],{},"min.insync.replicas"," 配合，否则 ISR 中只剩 Leader 一个副本时，依然可能成功写入。",[105,208,210],{"id":209},"_2-开启重试和幂等生产","2. 开启重试和幂等生产",[21,212,213],{},"网络抖动、Leader 切换等临时故障可能导致发送失败，生产者需要允许重试：",[183,215,217],{"className":185,"code":216,"language":187,"meta":188,"style":188},"enable.idempotence=true\nacks=all\nretries=2147483647\nmax.in.flight.requests.per.connection=5\ndelivery.timeout.ms=120000\n",[25,218,219,224,229,235,241],{"__ignoreMap":188},[192,220,221],{"class":194,"line":195},[192,222,223],{},"enable.idempotence=true\n",[192,225,227],{"class":194,"line":226},2,[192,228,186],{},[192,230,232],{"class":194,"line":231},3,[192,233,234],{},"retries=2147483647\n",[192,236,238],{"class":194,"line":237},4,[192,239,240],{},"max.in.flight.requests.per.connection=5\n",[192,242,244],{"class":194,"line":243},5,[192,245,246],{},"delivery.timeout.ms=120000\n",[21,248,249],{},"幂等生产者会给消息附加 Producer ID 和序列号，使 Broker 能识别同一生产会话内的重复写入，避免因重试产生重复消息。",[21,251,252],{},"需要注意：",[254,255,256,259,265],"ul",{},[85,257,258],{},"幂等生产解决的是重试导致的重复写入，不是发送失败后的业务补偿。",[85,260,261,264],{},[25,262,263],{},"delivery.timeout.ms"," 到期后，生产者仍可能最终失败。",[85,266,267],{},"业务不能无限依赖客户端重试，最终失败必须记录、告警或进入补偿流程。",[105,269,271],{"id":270},"_3-必须检查发送结果","3. 必须检查发送结果",[21,273,274],{},"下面这种“只发送、不处理结果”的写法存在风险：",[183,276,280],{"className":277,"code":278,"language":279,"meta":188,"style":188},"language-java shiki shiki-themes github-light github-dark","producer.send(record);\n","java",[25,281,282],{"__ignoreMap":188},[192,283,284],{"class":194,"line":195},[192,285,278],{},[21,287,288,289,292],{},"应该检查回调或 ",[25,290,291],{},"Future"," 的执行结果：",[183,294,296],{"className":277,"code":295,"language":279,"meta":188,"style":188},"producer.send(record, (metadata, exception) -> {\n    if (exception != null) {\n        \u002F\u002F 记录原始消息、告警并进入补偿流程\n        handleSendFailure(record, exception);\n    }\n});\n",[25,297,298,303,308,313,318,323],{"__ignoreMap":188},[192,299,300],{"class":194,"line":195},[192,301,302],{},"producer.send(record, (metadata, exception) -> {\n",[192,304,305],{"class":194,"line":226},[192,306,307],{},"    if (exception != null) {\n",[192,309,310],{"class":194,"line":231},[192,311,312],{},"        \u002F\u002F 记录原始消息、告警并进入补偿流程\n",[192,314,315],{"class":194,"line":237},[192,316,317],{},"        handleSendFailure(record, exception);\n",[192,319,320],{"class":194,"line":243},[192,321,322],{},"    }\n",[192,324,326],{"class":194,"line":325},6,[192,327,328],{},"});\n",[21,330,331,332,335],{},"序列化失败、鉴权失败、消息过大和超时等错误，最终都需要业务明确处理。不能把“调用过 ",[25,333,334],{},"send","”当成“消息已经可靠进入 Kafka”。",[105,337,339],{"id":338},"_4-数据库与-kafka-的一致性","4. 数据库与 Kafka 的一致性",[21,341,342],{},"如果业务流程是：",[183,344,349],{"className":345,"code":347,"language":348,"meta":188},[346],"language-text","更新数据库\n→\n发送 Kafka 消息\n","text",[25,350,347],{"__ignoreMap":188},[21,352,353],{},"数据库更新成功、消息发送失败时，仍然会造成业务事件丢失。",[21,355,356],{},"常见解决方式是本地消息表，也叫 Transactional Outbox：",[183,358,361],{"className":359,"code":360,"language":348,"meta":188},[346],"同一个数据库事务\n├── 更新业务数据\n└── 写入待发送事件表\n\n事务提交后\n→ 后台任务投递 Kafka\n→ 成功后标记事件已发送\n",[25,362,360],{"__ignoreMap":188},[21,364,365],{},"这样即使 Kafka 暂时不可用，消息也可以从本地消息表继续重试。Kafka 事务可以保证 Kafka 内部多条记录及消费 Offset 的原子性，但不会自动把外部数据库事务包含进来。",[14,367,369],{"id":368},"三broker-如何保证消息可靠保存","三、Broker 如何保证消息可靠保存",[105,371,373],{"id":372},"_1-合理设置副本数","1. 合理设置副本数",[21,375,376],{},"生产环境通常使用：",[183,378,381],{"className":379,"code":380,"language":348,"meta":188},[346],"replication.factor = 3\n",[25,382,380],{"__ignoreMap":188},[21,384,385],{},"一个分区包含一个 Leader 和多个 Follower。生产者与 Leader 交互，Follower 从 Leader 复制日志。某个 Broker 故障后，可以从仍然同步的副本中选举新 Leader。",[21,387,388],{},"副本数提高的是容错能力，但副本只有真正保持同步才有意义。",[105,390,392],{"id":391},"_2-理解-isr","2. 理解 ISR",[21,394,395],{},"ISR 是 In-Sync Replicas，即当前与 Leader 保持同步的副本集合。",[21,397,398],{},"例如一个三副本分区：",[183,400,403],{"className":401,"code":402,"language":348,"meta":188},[346],"Leader A\nFollower B\nFollower C\n\nISR = [A, B, C]\n",[25,404,402],{"__ignoreMap":188},[21,406,407],{},"如果 C 长时间跟不上 Leader，它会被移出 ISR：",[183,409,412],{"className":410,"code":411,"language":348,"meta":188},[346],"ISR = [A, B]\n",[25,413,411],{"__ignoreMap":188},[21,415,416,418],{},[25,417,27],{}," 等待的是当前 ISR 的确认，而不是永远等待所有配置副本。",[105,420,422,423],{"id":421},"_3-配置-mininsyncreplicas","3. 配置 ",[25,424,205],{},[21,426,427],{},"推荐组合：",[183,429,432],{"className":430,"code":431,"language":348,"meta":188},[346],"replication.factor = 3\nmin.insync.replicas = 2\nacks = all\n",[25,433,431],{"__ignoreMap":188},[21,435,436],{},"它表达的含义是：",[18,438,439],{},[21,440,441],{},"至少要有两个同步副本可用，写入才允许成功。",[21,443,444],{},"如果 ISR 只剩一个副本，Broker 会拒绝写入。此时系统牺牲部分可用性，避免在单副本状态下继续写入并承担更高的数据丢失风险。",[105,446,448],{"id":447},"_4-禁止非同步副本强行成为-leader","4. 禁止非同步副本强行成为 Leader",[183,450,452],{"className":185,"code":451,"language":187,"meta":188,"style":188},"unclean.leader.election.enable=false\n",[25,453,454],{"__ignoreMap":188},[192,455,456],{"class":194,"line":195},[192,457,451],{},[21,459,460],{},"如果允许非 ISR 副本成为 Leader，在所有同步副本不可用时，Kafka 可以更快恢复分区服务，但这个副本可能缺少最新消息，从而造成数据丢失。",[21,462,463],{},"这是一个典型的取舍：",[183,465,468],{"className":466,"code":467,"language":348,"meta":188},[346],"允许非同步副本选举：可用性更高，可能丢数据\n禁止非同步副本选举：一致性更强，分区可能暂时不可用\n",[25,469,467],{"__ignoreMap":188},[105,471,473,474,476,477],{"id":472},"_5-acksall-不等于每条消息都立即-fsync","5. ",[25,475,27],{}," 不等于每条消息都立即 ",[25,478,479],{},"fsync",[21,481,482,483,485],{},"Kafka 的持久性主要依赖顺序写日志、操作系统页缓存和多副本机制。Broker 返回成功，并不表示每个副本都对该消息单独执行了一次物理磁盘 ",[25,484,479],{},"。",[21,487,488],{},"实际生产中，通常通过跨 Broker 副本降低单机和单盘故障风险，而不是强制每条消息同步刷盘。若多个副本同时发生不可恢复故障，仍不存在数学意义上的绝对零丢失。",[14,490,492],{"id":491},"四消费者如何避免消费丢失","四、消费者如何避免“消费丢失”",[105,494,496],{"id":495},"_1-关闭自动提交-offset","1. 关闭自动提交 Offset",[183,498,500],{"className":185,"code":499,"language":187,"meta":188,"style":188},"enable.auto.commit=false\n",[25,501,502],{"__ignoreMap":188},[192,503,504],{"class":194,"line":195},[192,505,499],{},[21,507,508],{},"自动提交可能出现以下顺序：",[183,510,513],{"className":511,"code":512,"language":348,"meta":188},[346],"拉取消息\n→ 自动提交 Offset\n→ 执行业务\n→ 进程崩溃\n",[25,514,512],{"__ignoreMap":188},[21,516,517],{},"重启后，消费者会从已提交的下一个 Offset 继续消费，刚才尚未处理成功的消息就被跳过了。",[105,519,521],{"id":520},"_2-业务成功后再提交-offset","2. 业务成功后再提交 Offset",[21,523,524],{},"更可靠的顺序是：",[183,526,529],{"className":527,"code":528,"language":348,"meta":188},[346],"拉取消息\n→ 执行业务\n→ 业务成功\n→ 提交 Offset\n",[25,530,528],{"__ignoreMap":188},[21,532,533],{},"如果业务处理失败，就不提交 Offset，让消息后续重新消费：",[183,535,537],{"className":277,"code":536,"language":279,"meta":188,"style":188},"while (true) {\n    ConsumerRecords\u003CString, String> records = consumer.poll(Duration.ofSeconds(1));\n\n    for (ConsumerRecord\u003CString, String> record : records) {\n        process(record);\n    }\n\n    consumer.commitSync();\n}\n",[25,538,539,544,549,555,560,565,569,574,580],{"__ignoreMap":188},[192,540,541],{"class":194,"line":195},[192,542,543],{},"while (true) {\n",[192,545,546],{"class":194,"line":226},[192,547,548],{},"    ConsumerRecords\u003CString, String> records = consumer.poll(Duration.ofSeconds(1));\n",[192,550,551],{"class":194,"line":231},[192,552,554],{"emptyLinePlaceholder":553},true,"\n",[192,556,557],{"class":194,"line":237},[192,558,559],{},"    for (ConsumerRecord\u003CString, String> record : records) {\n",[192,561,562],{"class":194,"line":243},[192,563,564],{},"        process(record);\n",[192,566,567],{"class":194,"line":325},[192,568,322],{},[192,570,572],{"class":194,"line":571},7,[192,573,554],{"emptyLinePlaceholder":553},[192,575,577],{"class":194,"line":576},8,[192,578,579],{},"    consumer.commitSync();\n",[192,581,583],{"class":194,"line":582},9,[192,584,585],{},"}\n",[21,587,588],{},"示例表达的是基本原则。实际批量消费时，如果一批消息中只有部分成功，需要准确管理各分区可提交的 Offset，不能直接把失败消息之后的位置一起提交。",[105,590,592],{"id":591},"_3-为什么还需要业务幂等","3. 为什么还需要业务幂等",[21,594,595],{},"“处理成功后提交”可以避免消息被跳过，但会引入重复：",[183,597,600],{"className":598,"code":599,"language":348,"meta":188},[346],"业务处理成功\n→ 提交 Offset 前进程崩溃\n→ 重启后再次消费同一条消息\n",[25,601,599],{"__ignoreMap":188},[21,603,604],{},"因此，可靠消费通常采用至少一次投递，并在业务层保证幂等。常见方式包括：",[254,606,607,610,613,616,619],{},[85,608,609],{},"使用事件 ID 建立数据库唯一索引。",[85,611,612],{},"建立消费去重表或 Inbox 表。",[85,614,615],{},"使用业务状态机，只允许合法状态转换。",[85,617,618],{},"更新时带版本号或业务条件。",[85,620,621],{},"对扣款、发券等操作使用唯一业务流水号。",[18,623,624],{},[21,625,626],{},[41,627,628],{},"不丢消息通常意味着允许重复，再通过幂等消除重复影响。",[105,630,632],{"id":631},"_4-kafka-内部链路的-exactly-once","4. Kafka 内部链路的 Exactly Once",[21,634,635],{},"如果消费 Kafka 后仍然写回 Kafka，可以使用 Kafka 事务，把输出记录和消费 Offset 放进同一个事务：",[183,637,640],{"className":638,"code":639,"language":348,"meta":188},[346],"消费消息\n→ 处理\n→ 写入下游 Topic\n→ sendOffsetsToTransaction\n→ 提交事务\n",[25,641,639],{"__ignoreMap":188},[21,643,644],{},"下游消费者设置：",[183,646,648],{"className":185,"code":647,"language":187,"meta":188,"style":188},"isolation.level=read_committed\n",[25,649,650],{"__ignoreMap":188},[192,651,652],{"class":194,"line":195},[192,653,647],{},[21,655,656],{},"这样只读取已提交事务的数据。",[21,658,659],{},"但如果最终写入的是 MySQL 等外部系统，Kafka 事务并不能自动保证 Kafka 与数据库的原子性，仍需要数据库事务、幂等、Outbox 或 Inbox 等方案。",[14,661,663],{"id":662},"五三种投递语义","五、三种投递语义",[119,665,666,679],{},[122,667,668],{},[125,669,670,673,676],{},[128,671,672],{},"语义",[128,674,675],{},"Offset 与业务处理顺序",[128,677,678],{},"结果",[138,680,681,692,703],{},[125,682,683,686,689],{},[143,684,685],{},"At Most Once",[143,687,688],{},"先提交，再处理",[143,690,691],{},"可能丢失，通常不重复",[125,693,694,697,700],{},[143,695,696],{},"At Least Once",[143,698,699],{},"先处理，再提交",[143,701,702],{},"不轻易丢失，可能重复",[125,704,705,708,711],{},[143,706,707],{},"Exactly Once",[143,709,710],{},"事务或幂等机制协调",[143,712,713],{},"业务效果恰好一次",[21,715,716],{},"绝大多数业务系统采用：",[18,718,719],{},[21,720,721],{},[41,722,723],{},"Kafka 至少一次投递 + 消费端业务幂等。",[21,725,726],{},"这通常比追求所有环节的强事务更容易实现，也更便于扩展。",[14,728,730],{"id":729},"六常见故障与保障措施","六、常见故障与保障措施",[119,732,733,746],{},[122,734,735],{},[125,736,737,740,743],{},[128,738,739],{},"故障场景",[128,741,742],{},"可能后果",[128,744,745],{},"主要保障",[138,747,748,759,772,785,796,807,818,829],{},[125,749,750,753,756],{},[143,751,752],{},"生产者网络抖动",[143,754,755],{},"发送失败或结果未知",[143,757,758],{},"重试、幂等生产、回调处理",[125,760,761,764,767],{},[143,762,763],{},"Leader 写入后立即故障",[143,765,766],{},"未同步消息丢失",[143,768,769,771],{},[25,770,27],{},"、副本、ISR",[125,773,774,777,780],{},[143,775,776],{},"ISR 只剩一个副本",[143,778,779],{},"单点故障风险升高",[143,781,782,784],{},[25,783,205],{}," 拒绝写入",[125,786,787,790,793],{},[143,788,789],{},"同步副本全部不可用",[143,791,792],{},"错误选主后丢数据",[143,794,795],{},"禁止 unclean leader election",[125,797,798,801,804],{},[143,799,800],{},"消费者先提交后处理",[143,802,803],{},"业务消息被跳过",[143,805,806],{},"关闭自动提交，成功后提交",[125,808,809,812,815],{},[143,810,811],{},"处理成功但提交前崩溃",[143,813,814],{},"重复消费",[143,816,817],{},"业务幂等、唯一流水号",[125,819,820,823,826],{},[143,821,822],{},"数据库成功、Kafka 失败",[143,824,825],{},"业务事件未发出",[143,827,828],{},"Transactional Outbox",[125,830,831,834,837],{},[143,832,833],{},"Kafka 成功、下游失败",[143,835,836],{},"业务结果缺失",[143,838,839],{},"重试、死信、补偿、告警",[14,841,843],{"id":842},"七常见误区","七、常见误区",[105,845,847,848,850],{"id":846},"误区一配置-acksall-就绝对不会丢","误区一：配置 ",[25,849,27],{}," 就绝对不会丢",[21,852,853,855,856,858],{},[25,854,27],{}," 只解决生产者到 Broker 的确认强度，还要配合副本数、",[25,857,205],{},"、正确选主和消费者提交策略。",[105,860,862],{"id":861},"误区二配置无限重试就不会丢","误区二：配置无限重试就不会丢",[21,864,865,866,868],{},"重试仍然受 ",[25,867,263],{}," 等条件限制，而且权限错误、序列化错误等问题无法靠盲目重试解决。最终失败必须被业务感知和补偿。",[105,870,872],{"id":871},"误区三手动提交-offset-就是-exactly-once","误区三：手动提交 Offset 就是 Exactly Once",[21,874,875],{},"手动提交只能帮助建立“处理成功后提交”的顺序，崩溃窗口仍可能造成重复消费，因此还需要业务幂等。",[105,877,879],{"id":878},"误区四kafka-事务可以自动覆盖数据库","误区四：Kafka 事务可以自动覆盖数据库",[21,881,882],{},"Kafka 事务主要协调 Kafka 内部的消息与 Offset，不会自动与 MySQL 等外部数据库形成同一个原子事务。",[105,884,886],{"id":885},"误区五broker-返回成功就等于所有副本已经物理刷盘","误区五：Broker 返回成功就等于所有副本已经物理刷盘",[21,888,889],{},"Kafka 的确认、副本复制和磁盘刷盘是不同概念。高可靠主要依靠多副本和同步副本约束，而不是把每条消息都单独同步刷盘。",[14,891,893],{"id":892},"八监控和运维同样重要","八、监控和运维同样重要",[21,895,896],{},"配置正确后，还应持续监控：",[254,898,899,902,905,908,911,914],{},[85,900,901],{},"发送失败率、重试次数和发送延迟。",[85,903,904],{},"ISR 收缩、未充分复制分区和离线分区。",[85,906,907],{},"Broker 磁盘空间、磁盘延迟和网络异常。",[85,909,910],{},"消费积压、消费失败、重平衡次数。",[85,912,913],{},"死信消息和补偿任务堆积。",[85,915,916],{},"业务事件与最终结果的对账差异。",[21,918,919],{},"没有监控和对账，即使消息已经丢失，系统也可能长期无法发现。",[14,921,922],{"id":922},"参考资料",[254,924,925,933,940,947,954],{},[85,926,927],{},[63,928,932],{"href":929,"rel":930},"https:\u002F\u002Fkafka.apache.org\u002F41\u002Fconfiguration\u002Fproducer-configs\u002F",[931],"nofollow","Apache Kafka Producer Configs",[85,934,935],{},[63,936,939],{"href":937,"rel":938},"https:\u002F\u002Fkafka.apache.org\u002F41\u002Fconfiguration\u002Fbroker-configs\u002F",[931],"Apache Kafka Broker Configs",[85,941,942],{},[63,943,946],{"href":944,"rel":945},"https:\u002F\u002Fkafka.apache.org\u002F41\u002Fconfiguration\u002Fconsumer-configs\u002F",[931],"Apache Kafka Consumer Configs",[85,948,949],{},[63,950,953],{"href":951,"rel":952},"https:\u002F\u002Fkafka.apache.org\u002F41\u002Fdesign\u002Fdesign\u002F",[931],"Apache Kafka Design：Message Delivery Semantics",[85,955,956],{},[63,957,960],{"href":958,"rel":959},"https:\u002F\u002Fkafka.apache.org\u002F41\u002Fjavadoc\u002Forg\u002Fapache\u002Fkafka\u002Fclients\u002Fconsumer\u002FKafkaConsumer.html",[931],"Apache KafkaConsumer JavaDoc",[962,963,964],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":188,"searchDepth":226,"depth":226,"links":966},[967,968,969,970,977,986,992,993,994,1002,1003],{"id":16,"depth":226,"text":16},{"id":46,"depth":226,"text":46},{"id":76,"depth":226,"text":77},{"id":102,"depth":226,"text":103,"children":971},[972,974,975,976],{"id":107,"depth":231,"text":973},"1. 使用 acks=all",{"id":209,"depth":231,"text":210},{"id":270,"depth":231,"text":271},{"id":338,"depth":231,"text":339},{"id":368,"depth":226,"text":369,"children":978},[979,980,981,983,984],{"id":372,"depth":231,"text":373},{"id":391,"depth":231,"text":392},{"id":421,"depth":231,"text":982},"3. 配置 min.insync.replicas",{"id":447,"depth":231,"text":448},{"id":472,"depth":231,"text":985},"5. acks=all 不等于每条消息都立即 fsync",{"id":491,"depth":226,"text":492,"children":987},[988,989,990,991],{"id":495,"depth":231,"text":496},{"id":520,"depth":231,"text":521},{"id":591,"depth":231,"text":592},{"id":631,"depth":231,"text":632},{"id":662,"depth":226,"text":663},{"id":729,"depth":226,"text":730},{"id":842,"depth":226,"text":843,"children":995},[996,998,999,1000,1001],{"id":846,"depth":231,"text":997},"误区一：配置 acks=all 就绝对不会丢",{"id":861,"depth":231,"text":862},{"id":871,"depth":231,"text":872},{"id":878,"depth":231,"text":879},{"id":885,"depth":231,"text":886},{"id":892,"depth":226,"text":893},{"id":922,"depth":226,"text":922},"wiki:java:kafka-message-loss-prevention","从生产者、Broker 和消费者三段链路分析 Kafka 消息丢失的原因，以及 acks、ISR、副本、手动提交 Offset 和业务幂等的完整保障方案。","advanced",false,"md",{},72,"\u002Fjava\u002Fkafka-message-loss-prevention","面试专题",{"title":5,"description":1005},"java\u002Fkafka-message-loss-prevention","2026-07-23","UtvXba2UOX7L3Ax4YP6eRDXofDX2HpDZyhetgnWjZ6g",[1018,2437,3345],{"id":1019,"title":1020,"body":1021,"commentId":2428,"description":2429,"difficulty":1006,"draft":1007,"extension":1008,"meta":2430,"navigation":553,"order":2431,"path":2432,"section":2433,"seo":2434,"stem":2435,"updated":1015,"__hash__":2436},"java\u002Fjava\u002Fjdk8-synchronized-lock-states.md","JDK 8 synchronized 设计与锁状态转换",{"type":7,"value":1022,"toc":2372},[1023,1027,1033,1044,1047,1053,1056,1065,1070,1073,1078,1084,1088,1091,1097,1104,1107,1113,1116,1119,1167,1171,1174,1180,1186,1189,1198,1203,1207,1214,1229,1343,1349,1355,1358,1361,1367,1370,1374,1380,1383,1389,1396,1399,1432,1436,1439,1442,1445,1449,1452,1455,1462,1465,1470,1474,1477,1483,1486,1489,1492,1498,1501,1504,1507,1513,1516,1522,1528,1532,1539,1542,1548,1551,1562,1565,1568,1571,1577,1580,1583,1586,1590,1593,1596,1600,1603,1607,1610,1614,1617,1620,1625,1629,1632,1634,1637,1640,1646,1649,1652,1655,1659,1673,1676,1682,1685,1691,1694,1700,1703,1707,1710,1714,1717,1720,1723,1729,1735,1739,1742,1748,1751,1757,1760,1763,1767,1770,1784,1787,1790,1796,1799,1805,1809,1816,1822,1825,1831,1834,1837,1851,1854,1858,1863,1869,1875,1885,1890,1894,1897,1900,1909,1912,1915,1918,1922,2098,2102,2106,2112,2116,2119,2125,2128,2132,2138,2142,2145,2177,2180,2225,2228,2254,2257,2263,2266,2272,2276,2280,2283,2287,2290,2294,2297,2301,2304,2308,2311,2315,2318,2322,2359,2362,2370],[14,1024,1026],{"id":1025},"一先给出整体结论","一、先给出整体结论",[21,1028,1029,1032],{},[25,1030,1031],{},"synchronized"," 是 Java 语言提供的内置同步机制。它在语义上负责三件事：",[254,1034,1035,1038,1041],{},[85,1036,1037],{},"同一时刻只允许满足条件的线程进入临界区；",[85,1039,1040],{},"支持同一线程重复获取同一把锁，也就是可重入；",[85,1042,1043],{},"解锁 happens-before 后续对同一 Monitor 的加锁，保证相关共享数据的可见性和有序性。",[21,1045,1046],{},"在 JDK 8 HotSpot 中，为了避免所有同步操作都直接阻塞线程，JVM 会根据竞争情况使用不同实现：",[183,1048,1051],{"className":1049,"code":1050,"language":348,"meta":188},[346],"偏向锁：同一个线程反复进入，尽量避免原子操作\n轻量级锁：线程交替执行或短暂竞争，使用栈上 Lock Record 和 CAS\n重量级锁：竞争较强或必须使用 Monitor 能力时，通过 ObjectMonitor 管理等待线程\n",[25,1052,1050],{"__ignoreMap":188},[21,1054,1055],{},"通常所说的“无锁、偏向锁、轻量级锁、重量级锁”，是对对象头及其关联同步结构的概括，不是 Java 语言规范定义的四种锁类型。",[21,1057,1058],{},[63,1059,1062],{"href":1060,"target":66,"rel":1061},"\u002Fimages\u002Fwiki\u002Fjava\u002Fjdk8-synchronized-state-transitions.svg",[68,69],[71,1063],{"src":1060,"alt":1064},"JDK 8 synchronized 锁状态转换总览",[18,1066,1067],{},[21,1068,1069],{},"图中信息较多，可点击图片查看原图。",[21,1071,1072],{},"需要先纠正一个常见说法：",[18,1074,1075],{},[21,1076,1077],{},"“锁只能升级，不能降级”适合帮助理解竞争路径，但并不严谨。",[21,1079,1080,1081],{},"轻量级锁正常释放后会通过 CAS 恢复对象原来的 Mark Word；已经膨胀的 ObjectMonitor 也可能在后续安全点被 JVM 回收。更准确的说法是：",[41,1082,1083],{},"持锁竞争过程中通常不会为了当前请求立即从重量级锁退回轻量级锁，但对象状态并非终身只能单向变化。",[14,1085,1087],{"id":1086},"二synchronized-在字节码中的入口","二、synchronized 在字节码中的入口",[21,1089,1090],{},"同步代码块通常编译为：",[183,1092,1095],{"className":1093,"code":1094,"language":348,"meta":188},[346],"monitorenter\n   临界区代码\nmonitorexit\n",[25,1096,1094],{"__ignoreMap":188},[21,1098,1099,1100,1103],{},"编译器会为正常返回和异常退出路径生成对应的 ",[25,1101,1102],{},"monitorexit","，保证退出同步块时释放锁。",[21,1105,1106],{},"同步方法不会在方法体中出现这两个指令，而是在方法访问标志中使用：",[183,1108,1111],{"className":1109,"code":1110,"language":348,"meta":188},[346],"ACC_SYNCHRONIZED\n",[25,1112,1110],{"__ignoreMap":188},[21,1114,1115],{},"JVM 在调用和返回同步方法时完成 Monitor 的进入与退出。",[21,1117,1118],{},"无论入口是哪一种，最终都围绕同一个锁对象工作：",[119,1120,1121,1131],{},[122,1122,1123],{},[125,1124,1125,1128],{},[128,1126,1127],{},"写法",[128,1129,1130],{},"实际锁对象",[138,1132,1133,1144,1156],{},[125,1134,1135,1138],{},[143,1136,1137],{},"实例同步方法",[143,1139,1140,1141],{},"当前实例 ",[25,1142,1143],{},"this",[125,1145,1146,1149],{},[143,1147,1148],{},"静态同步方法",[143,1150,1151,1152,1155],{},"当前类对应的 ",[25,1153,1154],{},"Class"," 对象",[125,1157,1158,1161],{},[143,1159,1160],{},"同步代码块",[143,1162,1163,1166],{},[25,1164,1165],{},"synchronized (...)"," 中指定的对象",[14,1168,1170],{"id":1169},"三对象头与-mark-word","三、对象头与 Mark Word",[21,1172,1173],{},"普通 Java 对象的对象头主要包含：",[183,1175,1178],{"className":1176,"code":1177,"language":348,"meta":188},[346],"Mark Word\nKlass Pointer\n",[25,1179,1177],{"__ignoreMap":188},[21,1181,1182,1183,1185],{},"数组对象还会额外保存数组长度。",[25,1184,1031],{}," 的关键状态主要编码在 Mark Word 中。",[21,1187,1188],{},"以 JDK 8、64 位 HotSpot 为例，Mark Word 会复用同一段空间：",[21,1190,1191],{},[63,1192,1195],{"href":1193,"target":66,"rel":1194},"\u002Fimages\u002Fwiki\u002Fjava\u002Fjdk8-synchronized-mark-word.svg",[68,69],[71,1196],{"src":1193,"alt":1197},"JDK 8 HotSpot Mark Word 与同步结构",[18,1199,1200],{},[21,1201,1202],{},"可点击图片查看完整尺寸。",[105,1204,1206],{"id":1205},"统一按最低-3-位阅读","统一按最低 3 位阅读",[21,1208,1209,1210,1213],{},"为了避免一会儿看 2 位、一会儿看 3 位，本文后续统一展示 Mark Word 的最低 ",[41,1211,1212],{},"3 位","。但要先区分“展示宽度”和“字段含义”：",[254,1215,1216,1223,1226],{},[85,1217,1218,1219,1222],{},"真正的锁标志始终是最低 ",[41,1220,1221],{},"2 位","；",[85,1224,1225],{},"只有普通未锁定和偏向布局会把倒数第 3 位解释成“偏向位”；",[85,1227,1228],{},"轻量级锁、重量级锁保存的是对齐后的指针，本文把其最低 3 位也完整写出，但倒数第 3 位不再表示偏向。",[119,1230,1231,1247],{},[122,1232,1233],{},[125,1234,1235,1238,1241,1244],{},[128,1236,1237],{},"最低 3 位",[128,1239,1240],{},"最低 2 位锁标志",[128,1242,1243],{},"状态",[128,1245,1246],{},"倒数第 3 位如何解释",[138,1248,1249,1270,1289,1307,1325],{},[125,1250,1251,1256,1261,1264],{},[143,1252,1253],{},[25,1254,1255],{},"001",[143,1257,1258],{},[25,1259,1260],{},"01",[143,1262,1263],{},"普通未锁定",[143,1265,1266,1267],{},"偏向位为 ",[25,1268,1269],{},"0",[125,1271,1272,1277,1281,1284],{},[143,1273,1274],{},[25,1275,1276],{},"101",[143,1278,1279],{},[25,1280,1260],{},[143,1282,1283],{},"可偏向或已偏向",[143,1285,1266,1286],{},[25,1287,1288],{},"1",[125,1290,1291,1296,1301,1304],{},[143,1292,1293],{},[25,1294,1295],{},"000",[143,1297,1298],{},[25,1299,1300],{},"00",[143,1302,1303],{},"轻量级锁",[143,1305,1306],{},"对齐后的 Lock Record 指针低位，不是偏向位",[125,1308,1309,1314,1319,1322],{},[143,1310,1311],{},[25,1312,1313],{},"010",[143,1315,1316],{},[25,1317,1318],{},"10",[143,1320,1321],{},"重量级锁",[143,1323,1324],{},"对齐后的 ObjectMonitor 指针低位，不是偏向位",[125,1326,1327,1332,1337,1340],{},[143,1328,1329],{},[25,1330,1331],{},"011",[143,1333,1334],{},[25,1335,1336],{},"11",[143,1338,1339],{},"GC 标记",[143,1341,1342],{},"标记状态的一部分，不是偏向位",[21,1344,1345,1346,1348],{},"因此 ",[25,1347,1276],{}," 应当拆开读成：",[183,1350,1353],{"className":1351,"code":1352,"language":348,"meta":188},[346],"偏向位 1 ｜ 锁标志 01\n",[25,1354,1352],{"__ignoreMap":188},[21,1356,1357],{},"它不是一个三位的“锁标志”。本文使用三位只是为了让所有状态在图表中对齐、便于比较。",[21,1359,1360],{},"其中偏向状态可以继续区分：",[183,1362,1365],{"className":1363,"code":1364,"language":348,"meta":188},[346],"匿名偏向：线程指针为空，允许第一个线程认领\n已偏向：线程指针指向获得偏向的 JavaThread\n",[25,1366,1364],{"__ignoreMap":188},[21,1368,1369],{},"Mark Word 是一块被复用的空间。例如普通未锁定对象可以直接保存 identity hash；偏向对象需要保存线程指针，因此二者不能同时使用同一布局。",[105,1371,1373],{"id":1372},"epoch-到底是什么","epoch 到底是什么",[21,1375,1376,1379],{},[25,1377,1378],{},"epoch"," 可以理解为某个类的“偏向版本号”，它不是时间戳，也不是对象年龄。",[21,1381,1382],{},"偏向锁不只依赖对象自身。支持偏向的类会在类的 prototype header 中保存一个当前 epoch；每个偏向对象的 Mark Word 中也会记录对象认领时使用的 epoch。线程进入偏向对象时，会比较两者：",[183,1384,1387],{"className":1385,"code":1386,"language":348,"meta":188},[346],"对象 epoch == 类当前 epoch\n    → 这份偏向记录仍属于当前版本，继续检查偏向线程\n\n对象 epoch != 类当前 epoch\n    → 这份偏向记录已经过期，可以进入重偏向或撤销流程\n",[25,1388,1386],{"__ignoreMap":188},[21,1390,1391,1392,1395],{},"它主要服务于",[41,1393,1394],{},"批量重偏向","。当一批对象从线程 A 整体转交给线程 B 使用时，JVM 不必立即逐个改写所有对象头，而是先提升类的 epoch。旧对象之后被访问时，会因为 epoch 不匹配而按新版本重新处理。",[21,1397,1398],{},"所以需要把两个容易混淆的字段分开：",[119,1400,1401,1411],{},[122,1402,1403],{},[125,1404,1405,1408],{},[128,1406,1407],{},"字段",[128,1409,1410],{},"含义",[138,1412,1413,1423],{},[125,1414,1415,1420],{},[143,1416,1417],{},[25,1418,1419],{},"age",[143,1421,1422],{},"对象的 GC 年龄，用于分代回收",[125,1424,1425,1429],{},[143,1426,1427],{},[25,1428,1378],{},[143,1430,1431],{},"类的偏向版本，用于判断对象上的偏向记录是否过期",[14,1433,1435],{"id":1434},"四jdk-8-为什么需要三种锁实现","四、JDK 8 为什么需要三种锁实现",[21,1437,1438],{},"不同竞争场景适合不同成本的同步方式。",[105,1440,1441],{"id":1441},"只有一个线程反复进入",[21,1443,1444],{},"如果一个对象长期只被同一个线程加锁，每次都执行 CAS 仍然是额外成本。偏向锁把对象与线程关联起来，后续同一线程进入时主要检查对象头是否仍偏向自己。",[105,1446,1448],{"id":1447},"多线程交替执行但没有同时竞争","多线程交替执行，但没有同时竞争",[21,1450,1451],{},"例如线程 A 使用完后线程 B 才进入。此时没有必要挂起线程，可以通过栈上 Lock Record 和 CAS 完成加锁、解锁。",[105,1453,1454],{"id":1454},"多线程同时争抢或需要等待队列",[21,1456,1457,1458,1461],{},"当 CAS 快速路径无法解决竞争，或者调用 ",[25,1459,1460],{},"wait()"," 需要条件等待集合时，JVM 使用 ObjectMonitor 管理 Owner、竞争线程和等待线程。",[21,1463,1464],{},"所以这三种实现的目标不是简单比较“谁更高级”，而是：",[18,1466,1467],{},[21,1468,1469],{},"根据实际竞争强度，在原子操作、CPU 自旋、线程阻塞和唤醒成本之间做权衡。",[14,1471,1473],{"id":1472},"五对象初始状态普通未锁定还是匿名偏向","五、对象初始状态：普通未锁定还是匿名偏向",[21,1475,1476],{},"JDK 8 默认启用偏向锁，但默认存在启动延迟：",[183,1478,1481],{"className":1479,"code":1480,"language":348,"meta":188},[346],"-XX:+UseBiasedLocking\n-XX:BiasedLockingStartupDelay=4000\n",[25,1482,1480],{"__ignoreMap":188},[21,1484,1485],{},"因此需要区分对象创建时机：",[105,1487,1488],{"id":1488},"偏向锁尚未启用或被关闭",[21,1490,1491],{},"新对象通常使用普通未锁定布局：",[183,1493,1496],{"className":1494,"code":1495,"language":348,"meta":188},[346],"[identity hash | age | 0 | 01]\n",[25,1497,1495],{"__ignoreMap":188},[21,1499,1500],{},"线程第一次进入同步块时，直接尝试建立轻量级锁。",[105,1502,1503],{"id":1503},"偏向锁已经启用",[21,1505,1506],{},"支持偏向的类创建新对象时，对象通常处于匿名偏向状态：",[183,1508,1511],{"className":1509,"code":1510,"language":348,"meta":188},[346],"[0 | epoch | age | 1 | 01]\n",[25,1512,1510],{"__ignoreMap":188},[21,1514,1515],{},"因此严格来说，不应把这条路径描述为：",[183,1517,1520],{"className":1518,"code":1519,"language":348,"meta":188},[346],"新对象先是无锁，然后第一次进入才“升级”为可偏向状态\n",[25,1521,1519],{"__ignoreMap":188},[21,1523,1524,1525],{},"更准确的说法是：",[41,1526,1527],{},"对象创建时便根据类的 prototype header 获得普通未锁定或匿名偏向的 Mark Word。",[14,1529,1531],{"id":1530},"六偏向锁的获取与重入","六、偏向锁的获取与重入",[21,1533,1534,1535,1538],{},"线程进入一个匿名偏向对象时，会尝试通过 CAS 把自己的 ",[25,1536,1537],{},"JavaThread*","、类当前的 epoch 等信息写入 Mark Word。",[21,1540,1541],{},"成功后，对象变成：",[183,1543,1546],{"className":1544,"code":1545,"language":348,"meta":188},[346],"[当前 JavaThread* | epoch | age | 1 | 01]\n",[25,1547,1545],{"__ignoreMap":188},[21,1549,1550],{},"同一个线程以后再次进入时，主要检查：",[82,1552,1553,1556,1559],{},[85,1554,1555],{},"Mark Word 是否仍是偏向模式；",[85,1557,1558],{},"偏向线程是否是当前线程；",[85,1560,1561],{},"对象 epoch 是否仍与类的 prototype header 匹配。",[21,1563,1564],{},"全部满足时可直接进入，不需要再次通过 CAS 抢占锁。",[105,1566,1567],{"id":1567},"偏向锁退出为什么不清除线程信息",[21,1569,1570],{},"偏向锁针对的就是“同一个线程还会再次进入”这一场景。同步块结束后，Mark Word 通常仍保留对原线程的偏向：",[183,1572,1575],{"className":1573,"code":1574,"language":348,"meta":188},[346],"进入前：偏向线程 A\n执行中：偏向线程 A\n退出后：仍偏向线程 A\n",[25,1576,1574],{"__ignoreMap":188},[21,1578,1579],{},"它不是表示线程 A 永远占用临界区，而是表示下一次线程 A 再进入时可以走低成本路径。",[105,1581,1582],{"id":1582},"偏向锁如何实现可重入",[21,1584,1585],{},"对象头记录了偏向线程，但不通过一个公共计数器记录每次进入。JVM 可以结合当前线程栈上的锁记录识别同步嵌套。对同一偏向线程而言，重复进入不需要竞争性地修改对象头。",[14,1587,1589],{"id":1588},"七其他线程访问偏向对象时发生什么","七、其他线程访问偏向对象时发生什么",[21,1591,1592],{},"线程 B 遇到偏向线程 A 的对象时，不能简单地直接覆盖线程指针。JVM 需要先判断原偏向是否仍然有效。",[21,1594,1595],{},"可能出现以下路径。",[105,1597,1599],{"id":1598},"_1-epoch-已过期","1. epoch 已过期",[21,1601,1602],{},"类发生批量重偏向后，旧对象的 epoch 可能落后于类的 epoch。线程 B 可以尝试通过 CAS 将对象重新偏向自己。",[105,1604,1606],{"id":1605},"_2-原线程已经不再持有这把锁","2. 原线程已经不再持有这把锁",[21,1608,1609],{},"JVM 可以撤销原偏向，使对象恢复为普通未锁定状态，或者在允许的情况下重新偏向新线程。",[105,1611,1613],{"id":1612},"_3-原线程仍在同步块中","3. 原线程仍在同步块中",[21,1615,1616],{},"JVM 需要检查原线程栈，撤销偏向并重建合法的锁状态。如果此时存在实际竞争，后续会进入轻量级竞争或直接膨胀为 ObjectMonitor。",[21,1618,1619],{},"因此：",[18,1621,1622],{},[21,1623,1624],{},"另一个线程访问偏向对象，不等于必然直接升级为重量级锁。是否重偏向、撤销为普通状态、转换为轻量级锁或膨胀，需要结合 epoch、类级启发式统计和原线程是否仍持锁判断。",[14,1626,1628],{"id":1627},"八批量重偏向与批量撤销","八、批量重偏向与批量撤销",[21,1630,1631],{},"如果某个类的大量对象不断被不同线程使用，逐个在安全点撤销偏向的成本会很高。HotSpot 会对同一类的偏向撤销次数做启发式统计。",[105,1633,1394],{"id":1394},[21,1635,1636],{},"当撤销达到一定阈值时，JVM 可以提升类 prototype header 中的 epoch。旧对象不需要立刻逐个修改；之后线程发现对象 epoch 过期时，再尝试将它偏向当前线程。",[21,1638,1639],{},"适合这种模式：",[183,1641,1644],{"className":1642,"code":1643,"language":348,"meta":188},[346],"一批对象先由线程 A 使用\n之后整体交给线程 B 使用\n但同一时刻竞争并不强\n",[25,1645,1643],{"__ignoreMap":188},[105,1647,1648],{"id":1648},"批量撤销",[21,1650,1651],{},"如果该类持续表现出多线程竞争，偏向锁已经不再适合，JVM 可以撤销该类的偏向能力。之后新对象不再默认可偏向，已有对象也会按正常锁路径处理。",[21,1653,1654],{},"这也是为什么偏向锁不能只按“对象自身的四级升级”理解：它还存在以类为粒度的 prototype header、epoch 和批量启发式机制。",[14,1656,1658],{"id":1657},"九普通未锁定到轻量级锁","九、普通未锁定到轻量级锁",[21,1660,1661,1662,1665,1666,1669,1670,1672],{},"当对象处于普通未锁定状态，线程进入同步块时会在当前栈帧创建锁记录，HotSpot 源码中对应 ",[25,1663,1664],{},"BasicLock","，解释器栈中通常由 ",[25,1667,1668],{},"BasicObjectLock"," 将对象引用和 ",[25,1671,1664],{}," 组织在一起。",[21,1674,1675],{},"概念流程如下：",[183,1677,1680],{"className":1678,"code":1679,"language":348,"meta":188},[346],"1. 在线程栈创建 Lock Record\n2. 将对象原 Mark Word 保存到 displaced header\n3. CAS 修改对象 Mark Word\n4. 让 Mark Word 指向当前 Lock Record\n",[25,1681,1679],{"__ignoreMap":188},[21,1683,1684],{},"CAS 成功后：",[183,1686,1689],{"className":1687,"code":1688,"language":348,"meta":188},[346],"对象 Mark Word  →  线程栈 Lock Record\nLock Record     →  保存原 Mark Word\n",[25,1690,1688],{"__ignoreMap":188},[21,1692,1693],{},"对象头低两位为：",[183,1695,1698],{"className":1696,"code":1697,"language":348,"meta":188},[346],"00\n",[25,1699,1697],{"__ignoreMap":188},[21,1701,1702],{},"这就是通常所说的轻量级锁或栈锁。",[105,1704,1706],{"id":1705},"为什么要保存-displaced-header","为什么要保存 displaced header",[21,1708,1709],{},"轻量级锁占用了对象原来的 Mark Word。解锁时需要把原 Mark Word 恢复回对象头，因此先把它保存到 Lock Record。",[14,1711,1713],{"id":1712},"十轻量级锁的可重入","十、轻量级锁的可重入",[21,1715,1716],{},"同一个线程再次获取已经由自己栈锁定的对象时，JVM 会识别对象头中的 Lock Record 指针属于当前线程栈。",[21,1718,1719],{},"此时新的 Lock Record 会使用特殊值表示递归进入，例如 displaced header 置空，而不是再次覆盖对象头。",[21,1721,1722],{},"退出时：",[183,1724,1727],{"className":1725,"code":1726,"language":348,"meta":188},[346],"递归层 Lock Record：只退出当前递归层\n最外层 Lock Record：负责真正恢复对象头\n",[25,1728,1726],{"__ignoreMap":188},[21,1730,1731,1732,1734],{},"这解释了为什么 ",[25,1733,1031],{}," 天然支持可重入。",[14,1736,1738],{"id":1737},"十一轻量级锁如何释放","十一、轻量级锁如何释放",[21,1740,1741],{},"最外层同步块退出时，线程使用 CAS 尝试把 Lock Record 中保存的 displaced header 恢复到对象 Mark Word：",[183,1743,1746],{"className":1744,"code":1745,"language":348,"meta":188},[346],"期望值：对象头仍指向当前 Lock Record\n新值：进入同步块前保存的原 Mark Word\n",[25,1747,1745],{"__ignoreMap":188},[21,1749,1750],{},"CAS 成功：",[183,1752,1755],{"className":1753,"code":1754,"language":348,"meta":188},[346],"轻量级锁正常释放\n对象恢复普通未锁定状态\n",[25,1756,1754],{"__ignoreMap":188},[21,1758,1759],{},"CAS 失败通常意味着对象已在竞争过程中膨胀，不能再直接把原对象头覆盖回去，需要走 ObjectMonitor 的退出逻辑。",[21,1761,1762],{},"这就是“锁只能升级不能降级”表述不够准确的直接例子：没有发生膨胀的轻量级锁，退出后会恢复原 Mark Word。",[14,1764,1766],{"id":1765},"十二轻量级锁何时膨胀","十二、轻量级锁何时膨胀",[21,1768,1769],{},"线程尝试用 CAS 建立轻量级锁失败，说明对象头已经发生变化。可能原因包括：",[254,1771,1772,1775,1778,1781],{},[85,1773,1774],{},"另一个线程持有轻量级锁；",[85,1776,1777],{},"对象已经膨胀为 ObjectMonitor；",[85,1779,1780],{},"竞争期间其他线程正在处理锁状态；",[85,1782,1783],{},"必须执行依赖 ObjectMonitor 的操作。",[21,1785,1786],{},"JVM 可以先进行短暂自旋，希望持锁线程很快退出。自旋避免了线程立即挂起和恢复，但会消耗 CPU，因此只适合临界区较短的情况。",[21,1788,1789],{},"当快速路径不能解决问题时，JVM 会执行 Monitor inflation：",[183,1791,1794],{"className":1792,"code":1793,"language":348,"meta":188},[346],"创建或取得 ObjectMonitor\n复制并保存原 Mark Word\n将对象头改为指向 ObjectMonitor\n由 ObjectMonitor 负责后续竞争\n",[25,1795,1793],{"__ignoreMap":188},[21,1797,1798],{},"对象头低两位变成：",[183,1800,1803],{"className":1801,"code":1802,"language":348,"meta":188},[346],"10\n",[25,1804,1802],{"__ignoreMap":188},[14,1806,1808],{"id":1807},"十三重量级锁与-objectmonitor","十三、重量级锁与 ObjectMonitor",[21,1810,1811,1812,1815],{},"重量级状态下，对象 Mark Word 指向一个 ",[25,1813,1814],{},"ObjectMonitor","。理解它时重点关注以下字段或逻辑角色：",[183,1817,1820],{"className":1818,"code":1819,"language":348,"meta":188},[346],"_owner       当前持有 Monitor 的线程或相关锁记录\n_recursions  重入次数\n_cxq         新到达竞争线程形成的竞争队列\n_EntryList   等待重新竞争 Owner 的线程\n_WaitSet     调用 wait 后等待通知的线程\n_header      膨胀前保存的对象 Mark Word\n",[25,1821,1819],{"__ignoreMap":188},[21,1823,1824],{},"概念结构：",[183,1826,1829],{"className":1827,"code":1828,"language":348,"meta":188},[346],"对象 Mark Word\n      │\n      ▼\nObjectMonitor\n  ├─ Owner\n  ├─ Recursions\n  ├─ cxq \u002F EntryList\n  ├─ WaitSet\n  └─ displaced header\n",[25,1830,1828],{"__ignoreMap":188},[21,1832,1833],{},"竞争线程可能先自旋尝试获得 Owner。仍然失败时，线程会进入等待结构并被挂起；持有线程退出后，Monitor 选择或唤醒后继线程继续竞争。",[21,1835,1836],{},"“重量级”的成本主要来自：",[254,1838,1839,1842,1845,1848],{},[85,1840,1841],{},"维护竞争和等待队列；",[85,1843,1844],{},"线程阻塞、唤醒与调度；",[85,1846,1847],{},"高竞争下的上下文切换；",[85,1849,1850],{},"对 Monitor 状态的原子协调。",[21,1852,1853],{},"它并不意味着每次操作都必然立刻执行一次昂贵的系统调用，HotSpot 仍包含快速路径和自适应自旋等优化。",[14,1855,1857],{"id":1856},"十四waitnotify-为什么会涉及重量级-monitor","十四、wait、notify 为什么会涉及重量级 Monitor",[21,1859,1860,1862],{},[25,1861,1460],{}," 的语义不是普通锁竞争：",[183,1864,1867],{"className":1865,"code":1866,"language":348,"meta":188},[346],"确认当前线程持有 Monitor\n完整释放锁及其重入层数\n进入 WaitSet\n等待 notify、notifyAll、中断或超时\n重新参与锁竞争\n恢复原重入状态后返回\n",[25,1868,1866],{"__ignoreMap":188},[21,1870,1871,1872,1874],{},"这需要 Owner、WaitSet、EntryList、重入次数等完整 Monitor 能力，因此 JDK 8 HotSpot 执行 ",[25,1873,1460],{}," 时会确保对象已经膨胀为 ObjectMonitor。",[21,1876,1877,1880,1881,1884],{},[25,1878,1879],{},"notify()"," 和 ",[25,1882,1883],{},"notifyAll()"," 存在一个细节：如果对象当前只是由调用线程栈锁定，那么它不可能已经拥有 WaitSet，HotSpot 可以直接返回而不做无意义的膨胀；否则会取得或膨胀 ObjectMonitor，再处理等待线程。",[21,1886,1887,1889],{},[25,1888,1879],{}," 只是把等待线程从“等待条件”推进到“可以重新竞争锁”的阶段，并不会让它绕过当前 Owner 立即执行。",[14,1891,1893],{"id":1892},"十五identityhashcode-对锁状态的影响","十五、identityHashCode 对锁状态的影响",[21,1895,1896],{},"普通未锁定对象可以把 identity hash 保存在 Mark Word 中，但偏向锁的 Mark Word 需要保存 JavaThread 指针。",[21,1898,1899],{},"因此，对偏向对象计算：",[183,1901,1903],{"className":277,"code":1902,"language":279,"meta":188,"style":188},"System.identityHashCode(obj);\n",[25,1904,1905],{"__ignoreMap":188},[192,1906,1907],{"class":194,"line":195},[192,1908,1902],{},[21,1910,1911],{},"通常会导致偏向撤销，使对象头能够保存 hash。",[21,1913,1914],{},"如果对象正在使用轻量级锁，原 Mark Word 已经保存在当前线程栈的 Lock Record 中。为了稳定保存 identity hash，并让其他线程可靠读取，HotSpot 的相关路径可能把对象膨胀为 ObjectMonitor，再把 hash 保存在 Monitor 保存的 header 中。",[21,1916,1917],{},"所以观察锁升级实验时，不要在不知情的情况下调用可能计算 identity hash 的方法，否则实验本身会改变对象头。",[14,1919,1921],{"id":1920},"十六完整状态转换表","十六、完整状态转换表",[119,1923,1924,1937],{},[122,1925,1926],{},[125,1927,1928,1931,1934],{},[128,1929,1930],{},"当前状态",[128,1932,1933],{},"触发条件",[128,1935,1936],{},"可能结果",[138,1938,1939,1952,1965,1978,1990,2002,2014,2027,2039,2051,2063,2074,2087],{},[125,1940,1941,1946,1949],{},[143,1942,1943,1944],{},"普通未锁定 ",[25,1945,1255],{},[143,1947,1948],{},"首次进入同步块",[143,1950,1951],{},"CAS 成功后成为轻量级锁",[125,1953,1954,1959,1962],{},[143,1955,1956,1957],{},"匿名偏向 ",[25,1958,1276],{},[143,1960,1961],{},"第一个线程进入",[143,1963,1964],{},"CAS 写入线程指针，成为已偏向状态",[125,1966,1967,1972,1975],{},[143,1968,1969,1970],{},"已偏向 ",[25,1971,1276],{},[143,1973,1974],{},"原线程再次进入",[143,1976,1977],{},"保持偏向，低成本重入",[125,1979,1980,1984,1987],{},[143,1981,1969,1982],{},[25,1983,1276],{},[143,1985,1986],{},"新线程访问且 epoch 过期",[143,1988,1989],{},"尝试重偏向或撤销",[125,1991,1992,1996,1999],{},[143,1993,1969,1994],{},[25,1995,1276],{},[143,1997,1998],{},"新线程访问，原线程未持锁",[143,2000,2001],{},"撤销为普通状态，或按策略重偏向",[125,2003,2004,2008,2011],{},[143,2005,1969,2006],{},[25,2007,1276],{},[143,2009,2010],{},"新线程访问，原线程仍持锁",[143,2012,2013],{},"撤销偏向，转轻量级或膨胀",[125,2015,2016,2021,2024],{},[143,2017,2018,2019],{},"轻量级 ",[25,2020,1300],{},[143,2022,2023],{},"当前线程重入",[143,2025,2026],{},"新增递归 Lock Record，保持轻量级",[125,2028,2029,2033,2036],{},[143,2030,2018,2031],{},[25,2032,1300],{},[143,2034,2035],{},"无竞争退出",[143,2037,2038],{},"CAS 恢复原 Mark Word",[125,2040,2041,2045,2048],{},[143,2042,2018,2043],{},[25,2044,1300],{},[143,2046,2047],{},"竞争持续或恢复对象头失败",[143,2049,2050],{},"膨胀为 ObjectMonitor",[125,2052,2053,2056,2061],{},[143,2054,2055],{},"任意适用状态",[143,2057,2058,2060],{},[25,2059,1460],{}," 等需要完整 Monitor 语义",[143,2062,2050],{},[125,2064,2065,2068,2071],{},[143,2066,2067],{},"偏向\u002F轻量级",[143,2069,2070],{},"计算 identity hash 等特殊场景",[143,2072,2073],{},"撤销偏向或膨胀",[125,2075,2076,2081,2084],{},[143,2077,2078,2079],{},"重量级 ",[25,2080,1318],{},[143,2082,2083],{},"Owner 退出",[143,2085,2086],{},"唤醒\u002F选择后继；对象仍可保持膨胀",[125,2088,2089,2092,2095],{},[143,2090,2091],{},"空闲 ObjectMonitor",[143,2093,2094],{},"后续安全点清理",[143,2096,2097],{},"JVM 可能执行 Monitor deflation",[14,2099,2101],{"id":2100},"十七三条典型执行路径","十七、三条典型执行路径",[105,2103,2105],{"id":2104},"路径一同一个线程反复进入","路径一：同一个线程反复进入",[183,2107,2110],{"className":2108,"code":2109,"language":348,"meta":188},[346],"匿名偏向\n  → CAS 偏向线程 A\n  → A 执行同步块\n  → A 退出但保留偏向\n  → A 再次进入，快速命中\n",[25,2111,2109],{"__ignoreMap":188},[105,2113,2115],{"id":2114},"路径二线程交替使用没有重叠竞争","路径二：线程交替使用，没有重叠竞争",[21,2117,2118],{},"偏向关闭或偏向已撤销时：",[183,2120,2123],{"className":2121,"code":2122,"language":348,"meta":188},[346],"普通未锁定\n  → A 建立轻量级锁\n  → A CAS 恢复对象头\n  → B 建立轻量级锁\n  → B CAS 恢复对象头\n",[25,2124,2122],{"__ignoreMap":188},[21,2126,2127],{},"这种场景没有必要让线程进入阻塞等待。",[105,2129,2131],{"id":2130},"路径三多线程同时竞争","路径三：多线程同时竞争",[183,2133,2136],{"className":2134,"code":2135,"language":348,"meta":188},[346],"A 持有轻量级锁\n  → B CAS 失败并短暂自旋\n  → A 未及时释放\n  → 对象膨胀为 ObjectMonitor\n  → B 进入竞争\u002F等待结构\n  → A 退出并推进后继竞争\n",[25,2137,2135],{"__ignoreMap":188},[14,2139,2141],{"id":2140},"十八jol-验证时要注意什么","十八、JOL 验证时要注意什么",[21,2143,2144],{},"可以使用 JOL 查看对象布局：",[183,2146,2150],{"className":2147,"code":2148,"language":2149,"meta":188,"style":188},"language-xml shiki shiki-themes github-light github-dark","\u003Cdependency>\n    \u003CgroupId>org.openjdk.jol\u003C\u002FgroupId>\n    \u003CartifactId>jol-core\u003C\u002FartifactId>\n    \u003Cversion>0.17\u003C\u002Fversion>\n\u003C\u002Fdependency>\n","xml",[25,2151,2152,2157,2162,2167,2172],{"__ignoreMap":188},[192,2153,2154],{"class":194,"line":195},[192,2155,2156],{},"\u003Cdependency>\n",[192,2158,2159],{"class":194,"line":226},[192,2160,2161],{},"    \u003CgroupId>org.openjdk.jol\u003C\u002FgroupId>\n",[192,2163,2164],{"class":194,"line":231},[192,2165,2166],{},"    \u003CartifactId>jol-core\u003C\u002FartifactId>\n",[192,2168,2169],{"class":194,"line":237},[192,2170,2171],{},"    \u003Cversion>0.17\u003C\u002Fversion>\n",[192,2173,2174],{"class":194,"line":243},[192,2175,2176],{},"\u003C\u002Fdependency>\n",[21,2178,2179],{},"示例：",[183,2181,2183],{"className":277,"code":2182,"language":279,"meta":188,"style":188},"Object lock = new Object();\n\nSystem.out.println(ClassLayout.parseInstance(lock).toPrintable());\n\nsynchronized (lock) {\n    System.out.println(ClassLayout.parseInstance(lock).toPrintable());\n}\n\nSystem.out.println(ClassLayout.parseInstance(lock).toPrintable());\n",[25,2184,2185,2190,2194,2199,2203,2208,2213,2217,2221],{"__ignoreMap":188},[192,2186,2187],{"class":194,"line":195},[192,2188,2189],{},"Object lock = new Object();\n",[192,2191,2192],{"class":194,"line":226},[192,2193,554],{"emptyLinePlaceholder":553},[192,2195,2196],{"class":194,"line":231},[192,2197,2198],{},"System.out.println(ClassLayout.parseInstance(lock).toPrintable());\n",[192,2200,2201],{"class":194,"line":237},[192,2202,554],{"emptyLinePlaceholder":553},[192,2204,2205],{"class":194,"line":243},[192,2206,2207],{},"synchronized (lock) {\n",[192,2209,2210],{"class":194,"line":325},[192,2211,2212],{},"    System.out.println(ClassLayout.parseInstance(lock).toPrintable());\n",[192,2214,2215],{"class":194,"line":571},[192,2216,585],{},[192,2218,2219],{"class":194,"line":576},[192,2220,554],{"emptyLinePlaceholder":553},[192,2222,2223],{"class":194,"line":582},[192,2224,2198],{},[21,2226,2227],{},"实验时要控制以下变量：",[254,2229,2230,2233,2239,2245,2248,2251],{},[85,2231,2232],{},"明确使用 JDK 8 HotSpot；",[85,2234,2235,2236,1222],{},"记录是否开启 ",[25,2237,2238],{},"UseBiasedLocking",[85,2240,2241,2242,1222],{},"记录 ",[25,2243,2244],{},"BiasedLockingStartupDelay",[85,2246,2247],{},"避免日志、调试器或工具意外计算 identity hash；",[85,2249,2250],{},"JOL 输出会受 32\u002F64 位、压缩指针和 GC 配置影响；",[85,2252,2253],{},"线程已经终止不代表任何场景都一定重偏向，仍要看 epoch 和撤销策略。",[21,2255,2256],{},"为了避免默认 4 秒延迟影响实验，可以显式设置：",[183,2258,2261],{"className":2259,"code":2260,"language":348,"meta":188},[346],"-XX:+UseBiasedLocking\n-XX:BiasedLockingStartupDelay=0\n",[25,2262,2260],{"__ignoreMap":188},[21,2264,2265],{},"关闭偏向锁、直接观察轻量级路径：",[183,2267,2270],{"className":2268,"code":2269,"language":348,"meta":188},[346],"-XX:-UseBiasedLocking\n",[25,2271,2269],{"__ignoreMap":188},[14,2273,2275],{"id":2274},"十九常见误区","十九、常见误区",[105,2277,2279],{"id":2278},"误区一新对象一定先是无锁再升级为偏向锁","误区一：新对象一定先是无锁，再升级为偏向锁",[21,2281,2282],{},"不准确。偏向能力启用后，支持偏向的类可以让新对象直接使用匿名偏向的 prototype header。",[105,2284,2286],{"id":2285},"误区二第二个线程出现就一定升级为重量级锁","误区二：第二个线程出现就一定升级为重量级锁",[21,2288,2289],{},"不准确。它可能触发重偏向、偏向撤销、轻量级锁，也可能在存在实际竞争时膨胀。",[105,2291,2293],{"id":2292},"误区三轻量级锁就是一直自旋","误区三：轻量级锁就是一直自旋",[21,2295,2296],{},"不准确。轻量级锁的核心是栈上 Lock Record 与对象头 CAS；自旋是竞争时可能采用的等待优化，不是轻量级锁的完整定义。",[105,2298,2300],{"id":2299},"误区四重量级锁完全由操作系统-mutex-等同实现","误区四：重量级锁完全由操作系统 Mutex 等同实现",[21,2302,2303],{},"过度简化。ObjectMonitor 是 HotSpot 的 JVM 级同步结构，内部会使用 CAS、自旋、队列、park\u002Funpark 等机制；线程最终阻塞和唤醒会依赖操作系统能力，但两者不能直接画等号。",[105,2305,2307],{"id":2306},"误区五锁一旦膨胀这个对象终身都是重量级锁","误区五：锁一旦膨胀，这个对象终身都是重量级锁",[21,2309,2310],{},"不准确。当前竞争阶段通常不会立即降级，但空闲 Monitor 后续可能由 JVM 在安全点执行 deflation。",[105,2312,2314],{"id":2313},"误区六这套流程适用于所有-jdk","误区六：这套流程适用于所有 JDK",[21,2316,2317],{},"不准确。本文讨论的是 JDK 8 HotSpot。偏向锁后来被默认禁用，现代 HotSpot 的锁实现也持续演进，因此理解具体机制时必须先限定版本。",[14,2319,2321],{"id":2320},"二十参考资料","二十、参考资料",[254,2323,2324,2331,2338,2345,2352],{},[85,2325,2326],{},[63,2327,2330],{"href":2328,"rel":2329},"https:\u002F\u002Fgithub.com\u002Fopenjdk\u002Fjdk8u\u002Fblob\u002Fmaster\u002Fhotspot\u002Fsrc\u002Fshare\u002Fvm\u002Foops\u002FmarkOop.hpp",[931],"JDK 8 HotSpot：markOop.hpp",[85,2332,2333],{},[63,2334,2337],{"href":2335,"rel":2336},"https:\u002F\u002Fgithub.com\u002Fopenjdk\u002Fjdk8u\u002Fblob\u002Fmaster\u002Fhotspot\u002Fsrc\u002Fshare\u002Fvm\u002Fruntime\u002Fsynchronizer.cpp",[931],"JDK 8 HotSpot：synchronizer.cpp",[85,2339,2340],{},[63,2341,2344],{"href":2342,"rel":2343},"https:\u002F\u002Fgithub.com\u002Fopenjdk\u002Fjdk8u\u002Fblob\u002Fmaster\u002Fhotspot\u002Fsrc\u002Fshare\u002Fvm\u002Fruntime\u002FbiasedLocking.cpp",[931],"JDK 8 HotSpot：biasedLocking.cpp",[85,2346,2347],{},[63,2348,2351],{"href":2349,"rel":2350},"https:\u002F\u002Fgithub.com\u002Fopenjdk\u002Fjdk8u\u002Fblob\u002Fmaster\u002Fhotspot\u002Fsrc\u002Fshare\u002Fvm\u002Fruntime\u002FobjectMonitor.hpp",[931],"JDK 8 HotSpot：objectMonitor.hpp",[85,2353,2354],{},[63,2355,2358],{"href":2356,"rel":2357},"https:\u002F\u002Fwww.cnblogs.com\u002Fstar95\u002Fp\u002F17542850.html",[931],"参考文章：浅析 synchronized 锁升级的原理与实现",[14,2360,2361],{"id":2361},"相关内容",[254,2363,2364],{},[85,2365,2366],{},[63,2367,2369],{"href":2368},"\u002Fwiki\u002Fjava\u002Fsynchronized-vs-reentrantlock\u002F","synchronized 和 ReentrantLock 的区别",[962,2371,964],{},{"title":188,"searchDepth":226,"depth":226,"links":2373},[2374,2375,2376,2380,2385,2389,2393,2398,2402,2405,2406,2407,2408,2409,2410,2411,2412,2417,2418,2426,2427],{"id":1025,"depth":226,"text":1026},{"id":1086,"depth":226,"text":1087},{"id":1169,"depth":226,"text":1170,"children":2377},[2378,2379],{"id":1205,"depth":231,"text":1206},{"id":1372,"depth":231,"text":1373},{"id":1434,"depth":226,"text":1435,"children":2381},[2382,2383,2384],{"id":1441,"depth":231,"text":1441},{"id":1447,"depth":231,"text":1448},{"id":1454,"depth":231,"text":1454},{"id":1472,"depth":226,"text":1473,"children":2386},[2387,2388],{"id":1488,"depth":231,"text":1488},{"id":1503,"depth":231,"text":1503},{"id":1530,"depth":226,"text":1531,"children":2390},[2391,2392],{"id":1567,"depth":231,"text":1567},{"id":1582,"depth":231,"text":1582},{"id":1588,"depth":226,"text":1589,"children":2394},[2395,2396,2397],{"id":1598,"depth":231,"text":1599},{"id":1605,"depth":231,"text":1606},{"id":1612,"depth":231,"text":1613},{"id":1627,"depth":226,"text":1628,"children":2399},[2400,2401],{"id":1394,"depth":231,"text":1394},{"id":1648,"depth":231,"text":1648},{"id":1657,"depth":226,"text":1658,"children":2403},[2404],{"id":1705,"depth":231,"text":1706},{"id":1712,"depth":226,"text":1713},{"id":1737,"depth":226,"text":1738},{"id":1765,"depth":226,"text":1766},{"id":1807,"depth":226,"text":1808},{"id":1856,"depth":226,"text":1857},{"id":1892,"depth":226,"text":1893},{"id":1920,"depth":226,"text":1921},{"id":2100,"depth":226,"text":2101,"children":2413},[2414,2415,2416],{"id":2104,"depth":231,"text":2105},{"id":2114,"depth":231,"text":2115},{"id":2130,"depth":231,"text":2131},{"id":2140,"depth":226,"text":2141},{"id":2274,"depth":226,"text":2275,"children":2419},[2420,2421,2422,2423,2424,2425],{"id":2278,"depth":231,"text":2279},{"id":2285,"depth":231,"text":2286},{"id":2292,"depth":231,"text":2293},{"id":2299,"depth":231,"text":2300},{"id":2306,"depth":231,"text":2307},{"id":2313,"depth":231,"text":2314},{"id":2320,"depth":226,"text":2321},{"id":2361,"depth":226,"text":2361},"wiki:java:jdk8-synchronized-lock-states","从 Mark Word、Lock Record 与 ObjectMonitor 出发，系统理解 JDK 8 HotSpot 中偏向锁、轻量级锁、重量级锁的获取、撤销、膨胀和释放过程。",{},35,"\u002Fjava\u002Fjdk8-synchronized-lock-states","并发编程",{"title":1020,"description":2429},"java\u002Fjdk8-synchronized-lock-states","gORBab_xEnsWFF1EDOBMo_opFfosXf7qG31N3VQ4jOM",{"id":2438,"title":2369,"body":2439,"commentId":3335,"description":3336,"difficulty":3337,"draft":1007,"extension":1008,"meta":3338,"navigation":553,"order":3339,"path":3340,"section":1012,"seo":3341,"stem":3342,"updated":3343,"__hash__":3344},"java\u002Fjava\u002Fsynchronized-vs-reentrantlock.md",{"type":7,"value":2440,"toc":3314},[2441,2444,2447,2452,2455,2462,2464,2472,2482,2486,2488,2494,2497,2503,2506,2509,2515,2522,2525,2529,2682,2684,2688,2691,2696,2699,2705,2708,2711,2717,2720,2722,2726,2732,2735,2738,2744,2747,2752,2754,2758,2761,2764,2770,2773,2776,2782,2785,2791,2794,2800,2803,2805,2809,2812,2818,2821,2827,2832,2835,2837,2841,2846,2849,2855,2861,2867,2870,2876,2879,2885,2888,2894,2897,2903,2906,2912,2915,2917,2921,2924,2927,2933,2936,2942,2945,2948,2951,2957,2960,2963,2969,2972,2978,2985,2987,2991,2996,3002,3005,3008,3014,3021,3027,3030,3036,3039,3041,3045,3048,3051,3067,3070,3075,3078,3080,3084,3087,3107,3109,3115,3118,3123,3125,3129,3131,3157,3160,3166,3169,3171,3175,3220,3223,3228,3235,3241,3244,3246,3250,3254,3257,3261,3264,3268,3271,3279,3283,3286,3290,3293,3296,3302,3304,3307],[21,2442,2443],{},"synchronized和reentrantLock的区别？",[10,2445,2446],{"id":2446},"面试标准回答",[18,2448,2449],{},[21,2450,2451],{},"synchronized 是 JVM 层面的内置锁，语法简单，进入同步块后自动加锁，退出时自动释放，支持可重入和内存可见性。ReentrantLock 是基于 AQS 实现的显式锁，同样支持可重入，但提供了公平锁、可响应中断获取锁、tryLock、超时获取以及多个 Condition 等高级能力。两者在现代 JDK 中性能差距通常不是主要选型依据；功能简单时优先 synchronized，需要精细控制时使用 ReentrantLock，并且必须在 finally 中释放锁。",[21,2453,2454],{},"一句话记忆：",[18,2456,2457],{},[21,2458,2459],{},[41,2460,2461],{},"synchronized 简单自动，ReentrantLock 灵活可控。",[10,2463,46],{"id":46},[21,2465,2466,1880,2468,2471],{},[25,2467,1031],{},[25,2469,2470],{},"ReentrantLock"," 都能实现互斥和可见性，但定位不同：",[18,2473,2474],{},[21,2475,2476,2478,2479,2481],{},[25,2477,1031],{}," 是 JVM 原生关键字，语法简单；",[25,2480,2470],{}," 是 JUC 提供的显式锁，功能更丰富、控制能力更强。",[14,2483,2485],{"id":2484},"一基本用法","一、基本用法",[105,2487,1031],{"id":1031},[183,2489,2492],{"className":2490,"code":2491,"language":348},[346],"synchronized (lock) {\n    \u002F\u002F 临界区\n}\n",[25,2493,2491],{"__ignoreMap":188},[21,2495,2496],{},"或者：",[183,2498,2501],{"className":2499,"code":2500,"language":348},[346],"public synchronized void method() {\n}\n",[25,2502,2500],{"__ignoreMap":188},[21,2504,2505],{},"锁会自动释放。",[105,2507,2470],{"id":2508},"reentrantlock",[183,2510,2513],{"className":2511,"code":2512,"language":348},[346],"ReentrantLock lock = new ReentrantLock();\n\nlock.lock();\ntry {\n    \u002F\u002F 临界区\n} finally {\n    lock.unlock();\n}\n",[25,2514,2512],{"__ignoreMap":188},[21,2516,2517,2518,2521],{},"必须手动释放，所以通常必须写在 ",[25,2519,2520],{},"finally"," 中。",[2523,2524],"hr",{},[14,2526,2528],{"id":2527},"二核心区别","二、核心区别",[119,2530,2531,2542],{},[122,2532,2533],{},[125,2534,2535,2538,2540],{},[128,2536,2537],{},"对比项",[128,2539,1031],{},[128,2541,2470],{},[138,2543,2544,2555,2566,2576,2586,2599,2611,2623,2634,2649,2660,2671],{},[125,2545,2546,2549,2552],{},[143,2547,2548],{},"实现层次",[143,2550,2551],{},"JVM 关键字、Monitor",[143,2553,2554],{},"Java 类，基于 AQS",[125,2556,2557,2560,2563],{},[143,2558,2559],{},"加锁释放",[143,2561,2562],{},"自动",[143,2564,2565],{},"手动",[125,2567,2568,2571,2574],{},[143,2569,2570],{},"可重入",[143,2572,2573],{},"支持",[143,2575,2573],{},[125,2577,2578,2581,2584],{},[143,2579,2580],{},"公平锁",[143,2582,2583],{},"不支持显式配置",[143,2585,2573],{},[125,2587,2588,2591,2594],{},[143,2589,2590],{},"可中断获取锁",[143,2592,2593],{},"不支持",[143,2595,2596],{},[25,2597,2598],{},"lockInterruptibly()",[125,2600,2601,2604,2606],{},[143,2602,2603],{},"尝试获取锁",[143,2605,2593],{},[143,2607,2608],{},[25,2609,2610],{},"tryLock()",[125,2612,2613,2616,2618],{},[143,2614,2615],{},"超时获取锁",[143,2617,2593],{},[143,2619,2620],{},[25,2621,2622],{},"tryLock(timeout, unit)",[125,2624,2625,2628,2631],{},[143,2626,2627],{},"条件队列",[143,2629,2630],{},"一个 Monitor WaitSet",[143,2632,2633],{},"可创建多个 Condition",[125,2635,2636,2639,2644],{},[143,2637,2638],{},"等待通知",[143,2640,2641],{},[25,2642,2643],{},"wait\u002Fnotify\u002FnotifyAll",[143,2645,2646],{},[25,2647,2648],{},"await\u002Fsignal\u002FsignalAll",[125,2650,2651,2654,2657],{},[143,2652,2653],{},"锁状态查询",[143,2655,2656],{},"能力有限",[143,2658,2659],{},"提供较多查询方法",[125,2661,2662,2665,2668],{},[143,2663,2664],{},"编码复杂度",[143,2666,2667],{},"低",[143,2669,2670],{},"较高",[125,2672,2673,2676,2679],{},[143,2674,2675],{},"异常释放",[143,2677,2678],{},"自动释放",[143,2680,2681],{},"必须 finally unlock",[2523,2683],{},[10,2685,2687],{"id":2686},"三两者都支持可重入","三、两者都支持可重入",[21,2689,2690],{},"可重入指：",[18,2692,2693],{},[21,2694,2695],{},"同一个线程已经持有锁时，可以再次获取同一把锁。",[105,2697,1031],{"id":2698},"synchronized-1",[183,2700,2703],{"className":2701,"code":2702,"language":348},[346],"public synchronized void methodA() {\n    methodB();\n}\n\npublic synchronized void methodB() {\n}\n",[25,2704,2702],{"__ignoreMap":188},[21,2706,2707],{},"同一个对象上的两个同步方法，线程可以重入。",[105,2709,2470],{"id":2710},"reentrantlock-1",[183,2712,2715],{"className":2713,"code":2714,"language":348},[346],"lock.lock();\ntry {\n    lock.lock();\n    try {\n        \u002F\u002F 重入\n    } finally {\n        lock.unlock();\n    }\n} finally {\n    lock.unlock();\n}\n",[25,2716,2714],{"__ignoreMap":188},[21,2718,2719],{},"获取几次，就必须释放几次。",[2523,2721],{},[10,2723,2725],{"id":2724},"四reentrantlock-支持公平锁","四、ReentrantLock 支持公平锁",[183,2727,2730],{"className":2728,"code":2729,"language":348},[346],"ReentrantLock fairLock = new ReentrantLock(true);\n",[25,2731,2729],{"__ignoreMap":188},[21,2733,2734],{},"公平锁会尽量按 AQS 队列顺序获取锁。",[21,2736,2737],{},"默认是非公平锁：",[183,2739,2742],{"className":2740,"code":2741,"language":348},[346],"ReentrantLock lock = new ReentrantLock();\n",[25,2743,2741],{"__ignoreMap":188},[21,2745,2746],{},"非公平锁允许新线程插队，吞吐量通常更高。",[21,2748,2749,2751],{},[25,2750,1031],{}," 没有 API 让你指定公平性，通常按非公平竞争理解。",[2523,2753],{},[10,2755,2757],{"id":2756},"五reentrantlock-支持可响应中断等待","五、ReentrantLock 支持可响应中断等待",[21,2759,2760],{},"假设线程正在等待锁。",[21,2762,2763],{},"使用：",[183,2765,2768],{"className":2766,"code":2767,"language":348},[346],"lock.lock();\n",[25,2769,2767],{"__ignoreMap":188},[21,2771,2772],{},"即使线程被中断，也不会因为中断立即退出获取锁过程。",[21,2774,2775],{},"而：",[183,2777,2780],{"className":2778,"code":2779,"language":348},[346],"lock.lockInterruptibly();\n",[25,2781,2779],{"__ignoreMap":188},[21,2783,2784],{},"等待期间如果收到中断，会抛出：",[183,2786,2789],{"className":2787,"code":2788,"language":348},[346],"InterruptedException\n",[25,2790,2788],{"__ignoreMap":188},[21,2792,2793],{},"例如：",[183,2795,2798],{"className":2796,"code":2797,"language":348},[346],"try {\n    lock.lockInterruptibly();\n    try {\n        doWork();\n    } finally {\n        lock.unlock();\n    }\n} catch (InterruptedException e) {\n    Thread.currentThread().interrupt();\n}\n",[25,2799,2797],{"__ignoreMap":188},[21,2801,2802],{},"这对于避免线程长时间死等、响应取消请求很有价值。",[2523,2804],{},[10,2806,2808],{"id":2807},"六reentrantlock-支持尝试获取和超时","六、ReentrantLock 支持尝试获取和超时",[105,2810,2811],{"id":2811},"立即尝试",[183,2813,2816],{"className":2814,"code":2815,"language":348},[346],"if (lock.tryLock()) {\n    try {\n        doWork();\n    } finally {\n        lock.unlock();\n    }\n} else {\n    \u002F\u002F 获取失败，走降级逻辑\n}\n",[25,2817,2815],{"__ignoreMap":188},[105,2819,2820],{"id":2820},"超时尝试",[183,2822,2825],{"className":2823,"code":2824,"language":348},[346],"if (lock.tryLock(2, TimeUnit.SECONDS)) {\n    try {\n        doWork();\n    } finally {\n        lock.unlock();\n    }\n} else {\n    \u002F\u002F 两秒内未拿到锁\n}\n",[25,2826,2824],{"__ignoreMap":188},[21,2828,2829,2831],{},[25,2830,1031],{}," 一旦进入竞争，只能等待，无法直接设置超时。",[21,2833,2834],{},"这也是 ReentrantLock 在需要降级、超时控制时的重要优势。",[2523,2836],{},[10,2838,2840],{"id":2839},"七condition-比-waitnotify-更灵活","七、Condition 比 wait\u002Fnotify 更灵活",[21,2842,2843,2845],{},[25,2844,1031],{}," 的一个 Monitor 只有一个 WaitSet。",[21,2847,2848],{},"例如阻塞队列里：",[183,2850,2853],{"className":2851,"code":2852,"language":348},[346],"生产者等待 notFull\n消费者等待 notEmpty\n",[25,2854,2852],{"__ignoreMap":188},[21,2856,2857,2858,2860],{},"使用 ",[25,2859,1031],{}," 时，生产者和消费者都在同一个 WaitSet 中，通常要：",[183,2862,2865],{"className":2863,"code":2864,"language":348},[346],"notifyAll();\n",[25,2866,2864],{"__ignoreMap":188},[21,2868,2869],{},"而 ReentrantLock 可以创建多个 Condition：",[183,2871,2874],{"className":2872,"code":2873,"language":348},[346],"Condition notFull = lock.newCondition();\nCondition notEmpty = lock.newCondition();\n",[25,2875,2873],{"__ignoreMap":188},[21,2877,2878],{},"生产者等待：",[183,2880,2883],{"className":2881,"code":2882,"language":348},[346],"while (queue.size() == capacity) {\n    notFull.await();\n}\n",[25,2884,2882],{"__ignoreMap":188},[21,2886,2887],{},"消费者等待：",[183,2889,2892],{"className":2890,"code":2891,"language":348},[346],"while (queue.isEmpty()) {\n    notEmpty.await();\n}\n",[25,2893,2891],{"__ignoreMap":188},[21,2895,2896],{},"生产完成：",[183,2898,2901],{"className":2899,"code":2900,"language":348},[346],"notEmpty.signal();\n",[25,2902,2900],{"__ignoreMap":188},[21,2904,2905],{},"消费完成：",[183,2907,2910],{"className":2908,"code":2909,"language":348},[346],"notFull.signal();\n",[25,2911,2909],{"__ignoreMap":188},[21,2913,2914],{},"这样可以精确唤醒，减少无效唤醒。",[2523,2916],{},[10,2918,2920],{"id":2919},"八底层实现不同","八、底层实现不同",[14,2922,1031],{"id":2923},"synchronized-2",[21,2925,2926],{},"经典理解：",[183,2928,2931],{"className":2929,"code":2930,"language":348},[346],"对象头 Mark Word\n+\nMonitor\n+\nJVM 锁优化\n",[25,2932,2930],{"__ignoreMap":188},[21,2934,2935],{},"JDK 8 中可能经历：",[183,2937,2940],{"className":2938,"code":2939,"language":348},[346],"偏向锁\n→ 轻量级锁\n→ 重量级锁\n",[25,2941,2939],{"__ignoreMap":188},[21,2943,2944],{},"这是 HotSpot 的 JVM 实现优化。",[14,2946,2470],{"id":2947},"reentrantlock-2",[21,2949,2950],{},"主要基于：",[183,2952,2955],{"className":2953,"code":2954,"language":348},[346],"AbstractQueuedSynchronizer\n",[25,2956,2954],{"__ignoreMap":188},[21,2958,2959],{},"也就是 AQS。",[21,2961,2962],{},"核心状态：",[183,2964,2967],{"className":2965,"code":2966,"language":348},[346],"volatile int state;\n",[25,2968,2966],{"__ignoreMap":188},[21,2970,2971],{},"独占锁时：",[183,2973,2976],{"className":2974,"code":2975,"language":348},[346],"state = 0  未加锁\nstate = 1  第一次获取\nstate > 1  重入次数\n",[25,2977,2975],{"__ignoreMap":188},[21,2979,2980,2981,2984],{},"竞争失败的线程进入 CLH 变体同步队列，并通过 ",[25,2982,2983],{},"park\u002Funpark"," 等待和唤醒。",[2523,2986],{},[10,2988,2990],{"id":2989},"九异常情况下的差异","九、异常情况下的差异",[21,2992,2993,2995],{},[25,2994,1031],{},"：",[183,2997,3000],{"className":2998,"code":2999,"language":348},[346],"synchronized (lock) {\n    throw new RuntimeException();\n}\n",[25,3001,2999],{"__ignoreMap":188},[21,3003,3004],{},"离开同步块时，JVM 自动释放锁。",[21,3006,3007],{},"ReentrantLock：",[183,3009,3012],{"className":3010,"code":3011,"language":348},[346],"lock.lock();\ndoWork();\nlock.unlock();\n",[25,3013,3011],{"__ignoreMap":188},[21,3015,3016,3017,3020],{},"如果 ",[25,3018,3019],{},"doWork()"," 抛异常：",[183,3022,3025],{"className":3023,"code":3024,"language":348},[346],"unlock 没执行\n→ 锁永久未释放\n",[25,3026,3024],{"__ignoreMap":188},[21,3028,3029],{},"所以必须写：",[183,3031,3034],{"className":3032,"code":3033,"language":348},[346],"lock.lock();\ntry {\n    doWork();\n} finally {\n    lock.unlock();\n}\n",[25,3035,3033],{"__ignoreMap":188},[21,3037,3038],{},"这是 ReentrantLock 最常见的编码风险。",[2523,3040],{},[10,3042,3044],{"id":3043},"十性能区别","十、性能区别",[21,3046,3047],{},"早期 JDK 中，ReentrantLock 性能常常明显优于 synchronized。",[21,3049,3050],{},"但 JDK 6 以后，JVM 对 synchronized 做了大量优化：",[254,3052,3053,3056,3058,3061,3064],{},[85,3054,3055],{},"偏向锁",[85,3057,1303],{},[85,3059,3060],{},"自旋",[85,3062,3063],{},"锁消除",[85,3065,3066],{},"锁粗化",[21,3068,3069],{},"现代 JDK 中：",[18,3071,3072],{},[21,3073,3074],{},"两者性能差异通常不是选型的首要依据。",[21,3076,3077],{},"应该根据功能需求选择，而不是简单认为 ReentrantLock 一定更快。",[2523,3079],{},[10,3081,3083],{"id":3082},"十一什么时候使用-synchronized","十一、什么时候使用 synchronized",[21,3085,3086],{},"适合：",[254,3088,3089,3092,3095,3098,3101,3104],{},[85,3090,3091],{},"临界区简单",[85,3093,3094],{},"不需要公平锁",[85,3096,3097],{},"不需要超时获取",[85,3099,3100],{},"不需要中断锁等待",[85,3102,3103],{},"只有一个等待条件",[85,3105,3106],{},"希望代码简单、自动释放锁",[21,3108,2793],{},[183,3110,3113],{"className":3111,"code":3112,"language":348},[346],"public synchronized void increment() {\n    count++;\n}\n",[25,3114,3112],{"__ignoreMap":188},[21,3116,3117],{},"一般优先原则：",[18,3119,3120],{},[21,3121,3122],{},"能用 synchronized 清晰表达时，优先用 synchronized。",[2523,3124],{},[10,3126,3128],{"id":3127},"十二什么时候使用-reentrantlock","十二、什么时候使用 ReentrantLock",[21,3130,3086],{},[254,3132,3133,3136,3142,3145,3148,3151,3154],{},[85,3134,3135],{},"需要公平锁",[85,3137,3138,3139],{},"需要 ",[25,3140,3141],{},"tryLock",[85,3143,3144],{},"需要超时获取锁",[85,3146,3147],{},"需要可中断等待",[85,3149,3150],{},"需要多个 Condition",[85,3152,3153],{},"需要查询等待队列、持锁状态",[85,3155,3156],{},"需要更精细的锁控制",[21,3158,3159],{},"例如转账时避免死锁：",[183,3161,3164],{"className":3162,"code":3163,"language":348},[346],"if (accountA.lock.tryLock(100, TimeUnit.MILLISECONDS)) {\n    try {\n        if (accountB.lock.tryLock(100, TimeUnit.MILLISECONDS)) {\n            try {\n                transfer();\n            } finally {\n                accountB.lock.unlock();\n            }\n        }\n    } finally {\n        accountA.lock.unlock();\n    }\n}\n",[25,3165,3163],{"__ignoreMap":188},[21,3167,3168],{},"使用 synchronized 很难实现这种超时退避。",[2523,3170],{},[10,3172,3174],{"id":3173},"十三waitnotify-和-awaitsignal-对应关系","十三、wait\u002Fnotify 和 await\u002Fsignal 对应关系",[119,3176,3177,3185],{},[122,3178,3179],{},[125,3180,3181,3183],{},[128,3182,1031],{},[128,3184,2470],{},[138,3186,3187,3198,3209],{},[125,3188,3189,3193],{},[143,3190,3191],{},[25,3192,1460],{},[143,3194,3195],{},[25,3196,3197],{},"Condition.await()",[125,3199,3200,3204],{},[143,3201,3202],{},[25,3203,1879],{},[143,3205,3206],{},[25,3207,3208],{},"Condition.signal()",[125,3210,3211,3215],{},[143,3212,3213],{},[25,3214,1883],{},[143,3216,3217],{},[25,3218,3219],{},"Condition.signalAll()",[21,3221,3222],{},"两者都要求：",[18,3224,3225],{},[21,3226,3227],{},"调用等待或通知方法前，必须先持有对应的锁。",[21,3229,3230,3231,3234],{},"并且等待都应该放在 ",[25,3232,3233],{},"while"," 中：",[183,3236,3239],{"className":3237,"code":3238,"language":348},[346],"while (!conditionSatisfied()) {\n    condition.await();\n}\n",[25,3240,3238],{"__ignoreMap":188},[21,3242,3243],{},"防止虚假唤醒和竞争后条件失效。",[2523,3245],{},[10,3247,3249],{"id":3248},"十四常见误区","十四、常见误区",[105,3251,3253],{"id":3252},"误区一reentrantlock-才支持可重入","误区一：ReentrantLock 才支持可重入",[21,3255,3256],{},"不对。两者都可重入。",[105,3258,3260],{"id":3259},"误区二reentrantlock-一定比-synchronized-快","误区二：ReentrantLock 一定比 synchronized 快",[21,3262,3263],{},"不对。现代 JVM 下要看场景，功能差异比纯性能更重要。",[105,3265,3267],{"id":3266},"误区三synchronized-会自动释放reentrantlock-不会","误区三：synchronized 会自动释放，ReentrantLock 不会",[21,3269,3270],{},"更准确地说：",[254,3272,3273,3276],{},[85,3274,3275],{},"synchronized 离开同步块时自动释放",[85,3277,3278],{},"ReentrantLock 必须显式 unlock",[105,3280,3282],{"id":3281},"误区四公平锁一定更好","误区四：公平锁一定更好",[21,3284,3285],{},"公平锁吞吐量通常更低，只在有明确公平性需求时使用。",[105,3287,3289],{"id":3288},"误区五trylock-失败后可以直接-unlock","误区五：tryLock 失败后可以直接 unlock",[21,3291,3292],{},"不可以。只有成功获取锁后才能释放。",[21,3294,3295],{},"正确写法：",[183,3297,3300],{"className":3298,"code":3299,"language":348},[346],"boolean locked = false;\ntry {\n    locked = lock.tryLock();\n    if (!locked) {\n        return;\n    }\n\n    doWork();\n} finally {\n    if (locked) {\n        lock.unlock();\n    }\n}\n",[25,3301,3299],{"__ignoreMap":188},[2523,3303],{},[14,3305,3306],{"id":3306},"相关主题",[254,3308,3309],{},[85,3310,3311],{},[63,3312,1020],{"href":3313},"\u002Fwiki\u002Fjava\u002Fjdk8-synchronized-lock-states\u002F",{"title":188,"searchDepth":226,"depth":226,"links":3315},[3316,3320,3326,3327,3334],{"id":2484,"depth":226,"text":2485,"children":3317},[3318,3319],{"id":1031,"depth":231,"text":1031},{"id":2508,"depth":231,"text":2470},{"id":2527,"depth":226,"text":2528,"children":3321},[3322,3323,3324,3325],{"id":2698,"depth":231,"text":1031},{"id":2710,"depth":231,"text":2470},{"id":2811,"depth":231,"text":2811},{"id":2820,"depth":231,"text":2820},{"id":2923,"depth":226,"text":1031},{"id":2947,"depth":226,"text":2470,"children":3328},[3329,3330,3331,3332,3333],{"id":3252,"depth":231,"text":3253},{"id":3259,"depth":231,"text":3260},{"id":3266,"depth":231,"text":3267},{"id":3281,"depth":231,"text":3282},{"id":3288,"depth":231,"text":3289},{"id":3306,"depth":226,"text":3306},"wiki:java:synchronized-vs-reentrantlock","对比 synchronized 与 ReentrantLock 的实现、可重入、公平锁、中断、超时、Condition 和适用场景。","intermediate",{},71,"\u002Fjava\u002Fsynchronized-vs-reentrantlock",{"title":2369,"description":3336},"java\u002Fsynchronized-vs-reentrantlock","2026-07-21","vNahPOMAudgb7Mu0G7t_sn5sjTaBeUGcX25bRe10EXg",{"id":4,"title":5,"body":3346,"commentId":1004,"description":1005,"difficulty":1006,"draft":1007,"extension":1008,"meta":4046,"navigation":553,"order":1010,"path":1011,"section":1012,"seo":4047,"stem":1014,"updated":1015,"__hash__":1016},{"type":7,"value":3347,"toc":4011},[3348,3350,3352,3360,3362,3368,3370,3372,3378,3380,3387,3389,3391,3401,3403,3405,3409,3413,3457,3459,3467,3473,3475,3477,3501,3503,3505,3515,3517,3519,3527,3531,3559,3563,3565,3567,3572,3574,3576,3581,3583,3585,3587,3589,3594,3596,3598,3600,3602,3604,3609,3611,3616,3620,3624,3626,3631,3633,3637,3639,3641,3649,3651,3653,3658,3664,3668,3670,3672,3674,3682,3684,3689,3691,3693,3695,3700,3702,3742,3744,3746,3748,3753,3755,3767,3773,3775,3777,3782,3784,3792,3794,3796,3798,3836,3838,3844,3846,3848,3930,3932,3936,3942,3944,3948,3950,3952,3954,3956,3958,3960,3962,3964,3978,3980,3982,4009],[10,3349,5],{"id":12},[14,3351,16],{"id":16},[18,3353,3354],{},[21,3355,23,3356,28,3358,32],{},[25,3357,27],{},[25,3359,31],{},[21,3361,35],{},[18,3363,3364],{},[21,3365,3366],{},[41,3367,43],{},[14,3369,46],{"id":46},[21,3371,49],{},[18,3373,3374],{},[21,3375,3376],{},[41,3377,56],{},[21,3379,59],{},[21,3381,3382],{},[63,3383,3385],{"href":65,"target":66,"rel":3384},[68,69],[71,3386],{"src":65,"alt":73},[14,3388,77],{"id":76},[21,3390,80],{},[82,3392,3393,3395,3397,3399],{},[85,3394,87],{},[85,3396,90],{},[85,3398,93],{},[85,3400,96],{},[21,3402,99],{},[14,3404,103],{"id":102},[105,3406,108,3407],{"id":107},[25,3408,27],{},[21,3410,113,3411,117],{},[25,3412,116],{},[119,3414,3415,3425],{},[122,3416,3417],{},[125,3418,3419,3421,3423],{},[128,3420,130],{},[128,3422,133],{},[128,3424,136],{},[138,3426,3427,3437,3447],{},[125,3428,3429,3433,3435],{},[143,3430,3431],{},[25,3432,147],{},[143,3434,150],{},[143,3436,153],{},[125,3438,3439,3443,3445],{},[143,3440,3441],{},[25,3442,160],{},[143,3444,163],{},[143,3446,166],{},[125,3448,3449,3453,3455],{},[143,3450,3451],{},[25,3452,27],{},[143,3454,175],{},[143,3456,178],{},[21,3458,181],{},[183,3460,3461],{"className":185,"code":186,"language":187,"meta":188,"style":188},[25,3462,3463],{"__ignoreMap":188},[192,3464,3465],{"class":194,"line":195},[192,3466,186],{},[21,3468,3469,202,3471,206],{},[25,3470,27],{},[25,3472,205],{},[105,3474,210],{"id":209},[21,3476,213],{},[183,3478,3479],{"className":185,"code":216,"language":187,"meta":188,"style":188},[25,3480,3481,3485,3489,3493,3497],{"__ignoreMap":188},[192,3482,3483],{"class":194,"line":195},[192,3484,223],{},[192,3486,3487],{"class":194,"line":226},[192,3488,186],{},[192,3490,3491],{"class":194,"line":231},[192,3492,234],{},[192,3494,3495],{"class":194,"line":237},[192,3496,240],{},[192,3498,3499],{"class":194,"line":243},[192,3500,246],{},[21,3502,249],{},[21,3504,252],{},[254,3506,3507,3509,3513],{},[85,3508,258],{},[85,3510,3511,264],{},[25,3512,263],{},[85,3514,267],{},[105,3516,271],{"id":270},[21,3518,274],{},[183,3520,3521],{"className":277,"code":278,"language":279,"meta":188,"style":188},[25,3522,3523],{"__ignoreMap":188},[192,3524,3525],{"class":194,"line":195},[192,3526,278],{},[21,3528,288,3529,292],{},[25,3530,291],{},[183,3532,3533],{"className":277,"code":295,"language":279,"meta":188,"style":188},[25,3534,3535,3539,3543,3547,3551,3555],{"__ignoreMap":188},[192,3536,3537],{"class":194,"line":195},[192,3538,302],{},[192,3540,3541],{"class":194,"line":226},[192,3542,307],{},[192,3544,3545],{"class":194,"line":231},[192,3546,312],{},[192,3548,3549],{"class":194,"line":237},[192,3550,317],{},[192,3552,3553],{"class":194,"line":243},[192,3554,322],{},[192,3556,3557],{"class":194,"line":325},[192,3558,328],{},[21,3560,331,3561,335],{},[25,3562,334],{},[105,3564,339],{"id":338},[21,3566,342],{},[183,3568,3570],{"className":3569,"code":347,"language":348,"meta":188},[346],[25,3571,347],{"__ignoreMap":188},[21,3573,353],{},[21,3575,356],{},[183,3577,3579],{"className":3578,"code":360,"language":348,"meta":188},[346],[25,3580,360],{"__ignoreMap":188},[21,3582,365],{},[14,3584,369],{"id":368},[105,3586,373],{"id":372},[21,3588,376],{},[183,3590,3592],{"className":3591,"code":380,"language":348,"meta":188},[346],[25,3593,380],{"__ignoreMap":188},[21,3595,385],{},[21,3597,388],{},[105,3599,392],{"id":391},[21,3601,395],{},[21,3603,398],{},[183,3605,3607],{"className":3606,"code":402,"language":348,"meta":188},[346],[25,3608,402],{"__ignoreMap":188},[21,3610,407],{},[183,3612,3614],{"className":3613,"code":411,"language":348,"meta":188},[346],[25,3615,411],{"__ignoreMap":188},[21,3617,3618,418],{},[25,3619,27],{},[105,3621,422,3622],{"id":421},[25,3623,205],{},[21,3625,427],{},[183,3627,3629],{"className":3628,"code":431,"language":348,"meta":188},[346],[25,3630,431],{"__ignoreMap":188},[21,3632,436],{},[18,3634,3635],{},[21,3636,441],{},[21,3638,444],{},[105,3640,448],{"id":447},[183,3642,3643],{"className":185,"code":451,"language":187,"meta":188,"style":188},[25,3644,3645],{"__ignoreMap":188},[192,3646,3647],{"class":194,"line":195},[192,3648,451],{},[21,3650,460],{},[21,3652,463],{},[183,3654,3656],{"className":3655,"code":467,"language":348,"meta":188},[346],[25,3657,467],{"__ignoreMap":188},[105,3659,473,3660,476,3662],{"id":472},[25,3661,27],{},[25,3663,479],{},[21,3665,482,3666,485],{},[25,3667,479],{},[21,3669,488],{},[14,3671,492],{"id":491},[105,3673,496],{"id":495},[183,3675,3676],{"className":185,"code":499,"language":187,"meta":188,"style":188},[25,3677,3678],{"__ignoreMap":188},[192,3679,3680],{"class":194,"line":195},[192,3681,499],{},[21,3683,508],{},[183,3685,3687],{"className":3686,"code":512,"language":348,"meta":188},[346],[25,3688,512],{"__ignoreMap":188},[21,3690,517],{},[105,3692,521],{"id":520},[21,3694,524],{},[183,3696,3698],{"className":3697,"code":528,"language":348,"meta":188},[346],[25,3699,528],{"__ignoreMap":188},[21,3701,533],{},[183,3703,3704],{"className":277,"code":536,"language":279,"meta":188,"style":188},[25,3705,3706,3710,3714,3718,3722,3726,3730,3734,3738],{"__ignoreMap":188},[192,3707,3708],{"class":194,"line":195},[192,3709,543],{},[192,3711,3712],{"class":194,"line":226},[192,3713,548],{},[192,3715,3716],{"class":194,"line":231},[192,3717,554],{"emptyLinePlaceholder":553},[192,3719,3720],{"class":194,"line":237},[192,3721,559],{},[192,3723,3724],{"class":194,"line":243},[192,3725,564],{},[192,3727,3728],{"class":194,"line":325},[192,3729,322],{},[192,3731,3732],{"class":194,"line":571},[192,3733,554],{"emptyLinePlaceholder":553},[192,3735,3736],{"class":194,"line":576},[192,3737,579],{},[192,3739,3740],{"class":194,"line":582},[192,3741,585],{},[21,3743,588],{},[105,3745,592],{"id":591},[21,3747,595],{},[183,3749,3751],{"className":3750,"code":599,"language":348,"meta":188},[346],[25,3752,599],{"__ignoreMap":188},[21,3754,604],{},[254,3756,3757,3759,3761,3763,3765],{},[85,3758,609],{},[85,3760,612],{},[85,3762,615],{},[85,3764,618],{},[85,3766,621],{},[18,3768,3769],{},[21,3770,3771],{},[41,3772,628],{},[105,3774,632],{"id":631},[21,3776,635],{},[183,3778,3780],{"className":3779,"code":639,"language":348,"meta":188},[346],[25,3781,639],{"__ignoreMap":188},[21,3783,644],{},[183,3785,3786],{"className":185,"code":647,"language":187,"meta":188,"style":188},[25,3787,3788],{"__ignoreMap":188},[192,3789,3790],{"class":194,"line":195},[192,3791,647],{},[21,3793,656],{},[21,3795,659],{},[14,3797,663],{"id":662},[119,3799,3800,3810],{},[122,3801,3802],{},[125,3803,3804,3806,3808],{},[128,3805,672],{},[128,3807,675],{},[128,3809,678],{},[138,3811,3812,3820,3828],{},[125,3813,3814,3816,3818],{},[143,3815,685],{},[143,3817,688],{},[143,3819,691],{},[125,3821,3822,3824,3826],{},[143,3823,696],{},[143,3825,699],{},[143,3827,702],{},[125,3829,3830,3832,3834],{},[143,3831,707],{},[143,3833,710],{},[143,3835,713],{},[21,3837,716],{},[18,3839,3840],{},[21,3841,3842],{},[41,3843,723],{},[21,3845,726],{},[14,3847,730],{"id":729},[119,3849,3850,3860],{},[122,3851,3852],{},[125,3853,3854,3856,3858],{},[128,3855,739],{},[128,3857,742],{},[128,3859,745],{},[138,3861,3862,3870,3880,3890,3898,3906,3914,3922],{},[125,3863,3864,3866,3868],{},[143,3865,752],{},[143,3867,755],{},[143,3869,758],{},[125,3871,3872,3874,3876],{},[143,3873,763],{},[143,3875,766],{},[143,3877,3878,771],{},[25,3879,27],{},[125,3881,3882,3884,3886],{},[143,3883,776],{},[143,3885,779],{},[143,3887,3888,784],{},[25,3889,205],{},[125,3891,3892,3894,3896],{},[143,3893,789],{},[143,3895,792],{},[143,3897,795],{},[125,3899,3900,3902,3904],{},[143,3901,800],{},[143,3903,803],{},[143,3905,806],{},[125,3907,3908,3910,3912],{},[143,3909,811],{},[143,3911,814],{},[143,3913,817],{},[125,3915,3916,3918,3920],{},[143,3917,822],{},[143,3919,825],{},[143,3921,828],{},[125,3923,3924,3926,3928],{},[143,3925,833],{},[143,3927,836],{},[143,3929,839],{},[14,3931,843],{"id":842},[105,3933,847,3934,850],{"id":846},[25,3935,27],{},[21,3937,3938,855,3940,858],{},[25,3939,27],{},[25,3941,205],{},[105,3943,862],{"id":861},[21,3945,865,3946,868],{},[25,3947,263],{},[105,3949,872],{"id":871},[21,3951,875],{},[105,3953,879],{"id":878},[21,3955,882],{},[105,3957,886],{"id":885},[21,3959,889],{},[14,3961,893],{"id":892},[21,3963,896],{},[254,3965,3966,3968,3970,3972,3974,3976],{},[85,3967,901],{},[85,3969,904],{},[85,3971,907],{},[85,3973,910],{},[85,3975,913],{},[85,3977,916],{},[21,3979,919],{},[14,3981,922],{"id":922},[254,3983,3984,3989,3994,3999,4004],{},[85,3985,3986],{},[63,3987,932],{"href":929,"rel":3988},[931],[85,3990,3991],{},[63,3992,939],{"href":937,"rel":3993},[931],[85,3995,3996],{},[63,3997,946],{"href":944,"rel":3998},[931],[85,4000,4001],{},[63,4002,953],{"href":951,"rel":4003},[931],[85,4005,4006],{},[63,4007,960],{"href":958,"rel":4008},[931],[962,4010,964],{},{"title":188,"searchDepth":226,"depth":226,"links":4012},[4013,4014,4015,4016,4022,4029,4035,4036,4037,4044,4045],{"id":16,"depth":226,"text":16},{"id":46,"depth":226,"text":46},{"id":76,"depth":226,"text":77},{"id":102,"depth":226,"text":103,"children":4017},[4018,4019,4020,4021],{"id":107,"depth":231,"text":973},{"id":209,"depth":231,"text":210},{"id":270,"depth":231,"text":271},{"id":338,"depth":231,"text":339},{"id":368,"depth":226,"text":369,"children":4023},[4024,4025,4026,4027,4028],{"id":372,"depth":231,"text":373},{"id":391,"depth":231,"text":392},{"id":421,"depth":231,"text":982},{"id":447,"depth":231,"text":448},{"id":472,"depth":231,"text":985},{"id":491,"depth":226,"text":492,"children":4030},[4031,4032,4033,4034],{"id":495,"depth":231,"text":496},{"id":520,"depth":231,"text":521},{"id":591,"depth":231,"text":592},{"id":631,"depth":231,"text":632},{"id":662,"depth":226,"text":663},{"id":729,"depth":226,"text":730},{"id":842,"depth":226,"text":843,"children":4038},[4039,4040,4041,4042,4043],{"id":846,"depth":231,"text":997},{"id":861,"depth":231,"text":862},{"id":871,"depth":231,"text":872},{"id":878,"depth":231,"text":879},{"id":885,"depth":231,"text":886},{"id":892,"depth":226,"text":893},{"id":922,"depth":226,"text":922},{},{"title":5,"description":1005},1784814765432]