Files
2026-08-23 14:50:31 +00:00

1552 lines
60 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DCL 双重检查锁 详解</title>
<style>
:root {
--bg: #f8fafc;
--card: #ffffff;
--card2: #f1f5f9;
--accent: #0284c7;
--accent2: #6366f1;
--accent3: #db2777;
--accent4: #059669;
--accent5: #d97706;
--text: #1e293b;
--text2: #64748b;
--border: #e2e8f0;
--code-bg: #f1f5f9;
--danger: #dc2626;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans SC', sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.7;
overflow-x: hidden;
}
/* ===== Hero ===== */
.hero {
position: relative;
text-align: center;
padding: 80px 20px 60px;
background: linear-gradient(135deg, #e0f2fe 0%, #ede9fe 50%, #fce7f3 100%);
overflow: hidden;
}
.hero::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle at 30% 50%, rgba(56, 189, 248, 0.15) 0%, transparent 50%),
radial-gradient(circle at 70% 50%, rgba(129, 140, 248, 0.15) 0%, transparent 50%);
animation: heroFloat 20s ease-in-out infinite;
}
@keyframes heroFloat {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(-2%, 2%); }
}
.hero-content {
position: relative;
z-index: 1;
}
.hero h1 {
font-size: 3.2rem;
font-weight: 800;
background: linear-gradient(135deg, var(--accent), var(--accent2), var(--accent3));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 12px;
}
.hero .subtitle {
font-size: 1.3rem;
color: var(--text2);
margin-bottom: 24px;
}
.hero .badge-row {
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
}
.badge {
padding: 6px 16px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
border: 1px solid;
}
.badge.blue { background: rgba(56,189,248,.15); color: var(--accent); border-color: rgba(56,189,248,.4); }
.badge.purple { background: rgba(129,140,248,.15); color: var(--accent2); border-color: rgba(129,140,248,.4); }
.badge.pink { background: rgba(244,114,182,.15); color: var(--accent3); border-color: rgba(244,114,182,.4); }
.badge.green { background: rgba(52,211,153,.15); color: var(--accent4); border-color: rgba(52,211,153,.4); }
/* ===== Container ===== */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
/* ===== Section ===== */
.section {
margin: 48px 0;
}
.section-title {
display: flex;
align-items: center;
gap: 12px;
font-size: 1.6rem;
font-weight: 700;
margin-bottom: 24px;
padding-bottom: 12px;
border-bottom: 2px solid var(--border);
}
.section-title .icon {
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
flex-shrink: 0;
}
.icon.blue { background: rgba(56,189,248,.2); }
.icon.purple { background: rgba(129,140,248,.2); }
.icon.pink { background: rgba(244,114,182,.2); }
.icon.green { background: rgba(52,211,153,.2); }
.icon.yellow { background: rgba(251,191,36,.2); }
.icon.red { background: rgba(239,68,68,.2); }
/* ===== Cards ===== */
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 28px;
margin-bottom: 20px;
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 30px rgba(0,0,0,.1);
}
.card h3 {
font-size: 1.15rem;
margin-bottom: 12px;
color: var(--accent);
}
.card p, .card li {
color: var(--text2);
font-size: 0.95rem;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.grid-3 {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
}
@media (max-width: 768px) {
.grid-2, .grid-3 { grid-template-columns: 1fr; }
.hero h1 { font-size: 2rem; }
}
/* ===== Code Block ===== */
.code-block {
background: var(--code-bg);
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
margin: 16px 0;
}
.code-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
background: rgba(0,0,0,.03);
border-bottom: 1px solid var(--border);
}
.code-header .dots {
display: flex;
gap: 6px;
}
.code-header .dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.dot.r { background: #ef4444; }
.dot.y { background: #fbbf24; }
.dot.g { background: #34d399; }
.code-header .lang {
font-size: 0.8rem;
color: var(--text2);
font-weight: 600;
}
.code-block pre {
padding: 20px;
overflow-x: auto;
font-size: 0.88rem;
line-height: 1.65;
}
.code-block code {
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
}
/* Syntax Colors */
.kw { color: #8839ef; }
.type { color: #df8e1d; }
.str { color: #40a02b; }
.cm { color: #8c8fa1; font-style: italic; }
.fn { color: #1e66f5; }
.num { color: #fe640b; }
.ann { color: #d20f39; }
.op { color: #179299; }
.var { color: #d20f39; }
.field { color: #df8e1d; }
/* ===== Flow Diagram ===== */
.flow-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 0;
padding: 20px 0;
}
.flow-step {
background: var(--card2);
border: 1px solid var(--border);
border-radius: 12px;
padding: 16px 28px;
text-align: center;
min-width: 320px;
max-width: 500px;
position: relative;
}
.flow-step .step-num {
position: absolute;
top: -12px;
left: 20px;
background: var(--accent);
color: #ffffff;
font-size: 0.75rem;
font-weight: 800;
padding: 2px 10px;
border-radius: 10px;
}
.flow-step .step-title {
font-weight: 700;
font-size: 1rem;
margin-bottom: 4px;
}
.flow-step .step-desc {
font-size: 0.85rem;
color: var(--text2);
}
.flow-arrow {
width: 2px;
height: 28px;
background: var(--accent);
position: relative;
}
.flow-arrow::after {
content: '';
position: absolute;
bottom: -4px;
left: 50%;
transform: translateX(-50%);
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 8px solid var(--accent);
}
.flow-arrow.branch-left {
align-self: flex-start;
margin-left: calc(50% - 200px);
}
/* ===== Highlight Box ===== */
.highlight-box {
border-radius: 12px;
padding: 20px 24px;
margin: 16px 0;
border-left: 4px solid;
}
.highlight-box.info {
background: rgba(56,189,248,.1);
border-color: var(--accent);
}
.highlight-box.warn {
background: rgba(251,191,36,.1);
border-color: var(--accent5);
}
.highlight-box.danger {
background: rgba(239,68,68,.1);
border-color: var(--danger);
}
.highlight-box.success {
background: rgba(52,211,153,.1);
border-color: var(--accent4);
}
.highlight-box h4 {
font-size: 1rem;
margin-bottom: 8px;
}
.highlight-box.info h4 { color: var(--accent); }
.highlight-box.warn h4 { color: var(--accent5); }
.highlight-box.danger h4 { color: var(--danger); }
.highlight-box.success h4 { color: var(--accent4); }
.highlight-box p, .highlight-box li {
font-size: 0.92rem;
color: var(--text2);
}
/* ===== Timeline ===== */
.timeline {
position: relative;
padding-left: 40px;
}
.timeline::before {
content: '';
position: absolute;
left: 15px;
top: 0;
bottom: 0;
width: 2px;
background: linear-gradient(to bottom, var(--accent), var(--accent2), var(--accent3));
}
.timeline-item {
position: relative;
margin-bottom: 28px;
}
.timeline-item::before {
content: '';
position: absolute;
left: -32px;
top: 6px;
width: 14px;
height: 14px;
border-radius: 50%;
border: 3px solid var(--accent);
background: var(--bg);
}
.timeline-item:nth-child(2)::before { border-color: var(--accent2); }
.timeline-item:nth-child(3)::before { border-color: var(--accent3); }
.timeline-item:nth-child(4)::before { border-color: var(--accent4); }
.timeline-item:nth-child(5)::before { border-color: var(--accent5); }
.timeline-item h4 {
font-size: 1rem;
margin-bottom: 4px;
}
.timeline-item p {
font-size: 0.9rem;
color: var(--text2);
}
/* ===== Table ===== */
.styled-table {
width: 100%;
border-collapse: collapse;
border-radius: 12px;
overflow: hidden;
margin: 16px 0;
}
.styled-table th {
background: rgba(56,189,248,.12);
color: var(--accent);
padding: 14px 16px;
text-align: left;
font-size: 0.9rem;
font-weight: 700;
}
.styled-table td {
padding: 12px 16px;
border-top: 1px solid var(--border);
font-size: 0.9rem;
color: var(--text2);
}
.styled-table tr:hover td {
background: rgba(0,0,0,.02);
}
/* ===== Comparison ===== */
.vs-container {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 16px;
align-items: start;
}
.vs-divider {
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
font-weight: 800;
color: var(--accent5);
padding-top: 60px;
}
@media (max-width: 768px) {
.vs-container { grid-template-columns: 1fr; }
.vs-divider { padding-top: 0; }
}
.vs-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 24px;
}
.vs-card h3 {
text-align: center;
margin-bottom: 16px;
font-size: 1.1rem;
}
.vs-card.bad h3 { color: var(--danger); }
.vs-card.good h3 { color: var(--accent4); }
.vs-card ul {
list-style: none;
padding: 0;
}
.vs-card li {
padding: 6px 0;
font-size: 0.9rem;
color: var(--text2);
display: flex;
align-items: flex-start;
gap: 8px;
}
.vs-card li::before {
flex-shrink: 0;
margin-top: 2px;
}
.vs-card.bad li::before { content: '✗'; color: var(--danger); font-weight: 700; }
.vs-card.good li::before { content: '✓'; color: var(--accent4); font-weight: 700; }
/* ===== Instruction Reorder Visual ===== */
.reorder-visual {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
margin: 20px 0;
}
@media (max-width: 768px) {
.reorder-visual { grid-template-columns: 1fr; }
}
.reorder-col {
background: var(--card2);
border-radius: 12px;
padding: 20px;
border: 1px solid var(--border);
}
.reorder-col h4 {
text-align: center;
margin-bottom: 16px;
font-size: 0.95rem;
}
.reorder-col.wrong h4 { color: var(--danger); }
.reorder-col.right h4 { color: var(--accent4); }
.inst-line {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
margin: 6px 0;
border-radius: 8px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.85rem;
}
.inst-line .line-num {
color: var(--text2);
font-size: 0.75rem;
min-width: 20px;
}
.reorder-col.wrong .inst-line {
background: rgba(239,68,68,.08);
border: 1px solid rgba(239,68,68,.2);
}
.reorder-col.right .inst-line {
background: rgba(52,211,153,.08);
border: 1px solid rgba(52,211,153,.2);
}
.reorder-arrow-group {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
margin: 12px 0;
}
.reorder-arrow-group .arrow-text {
font-size: 0.78rem;
color: var(--accent5);
font-weight: 600;
}
/* ===== Thread Diagram ===== */
.thread-diagram {
background: var(--card2);
border-radius: 16px;
padding: 24px;
border: 1px solid var(--border);
margin: 20px 0;
overflow-x: auto;
}
.thread-row {
display: grid;
grid-template-columns: 80px 1fr 1fr;
gap: 16px;
align-items: center;
padding: 8px 0;
}
.thread-row.header {
border-bottom: 2px solid var(--border);
padding-bottom: 12px;
margin-bottom: 8px;
}
.thread-label {
font-weight: 700;
font-size: 0.85rem;
text-align: center;
padding: 6px 12px;
border-radius: 8px;
}
.thread-label.t1 { background: rgba(56,189,248,.15); color: var(--accent); }
.thread-label.t2 { background: rgba(244,114,182,.15); color: var(--accent3); }
.thread-action {
font-size: 0.85rem;
color: var(--text2);
padding: 6px 12px;
border-radius: 6px;
background: rgba(0,0,0,.03);
font-family: 'JetBrains Mono', monospace;
}
.thread-action.active {
background: rgba(56,189,248,.1);
color: var(--accent);
border: 1px solid rgba(56,189,248,.2);
}
.thread-action.danger {
background: rgba(239,68,68,.1);
color: var(--danger);
border: 1px solid rgba(239,68,68,.2);
}
.thread-action.empty {
background: transparent;
color: var(--text2);
opacity: 0.4;
}
.step-tag {
display: inline-block;
font-size: 0.7rem;
font-weight: 700;
padding: 2px 8px;
border-radius: 4px;
margin-right: 6px;
}
.step-tag.s1 { background: rgba(56,189,248,.25); color: var(--accent); }
.step-tag.s2 { background: rgba(129,140,248,.25); color: var(--accent2); }
.step-tag.s3 { background: rgba(244,114,182,.25); color: var(--accent3); }
.step-tag.s4 { background: rgba(52,211,153,.25); color: var(--accent4); }
/* ===== Memory Model ===== */
.memory-visual {
display: flex;
gap: 24px;
align-items: stretch;
margin: 20px 0;
flex-wrap: wrap;
}
.memory-box {
flex: 1;
min-width: 200px;
background: var(--card2);
border-radius: 12px;
padding: 20px;
border: 1px solid var(--border);
text-align: center;
}
.memory-box h4 {
margin-bottom: 12px;
font-size: 0.95rem;
}
.memory-box .mem-item {
padding: 8px;
margin: 6px 0;
border-radius: 6px;
font-size: 0.85rem;
font-family: 'JetBrains Mono', monospace;
}
.memory-connector {
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
color: var(--accent5);
}
/* ===== Pros/Cons ===== */
.pros-cons {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 768px) {
.pros-cons { grid-template-columns: 1fr; }
}
.pros-card, .cons-card {
border-radius: 16px;
padding: 24px;
border: 1px solid;
}
.pros-card {
background: rgba(52,211,153,.08);
border-color: rgba(52,211,153,.25);
}
.cons-card {
background: rgba(239,68,68,.08);
border-color: rgba(239,68,68,.25);
}
.pros-card h3 { color: var(--accent4); }
.cons-card h3 { color: var(--danger); }
.pros-card li, .cons-card li {
padding: 6px 0;
font-size: 0.9rem;
color: var(--text2);
list-style: none;
}
.pros-card li::before { content: '✅ '; }
.cons-card li::before { content: '⚠️ '; }
/* ===== Footer ===== */
.footer {
text-align: center;
padding: 40px 20px;
border-top: 1px solid var(--border);
margin-top: 60px;
color: var(--text2);
font-size: 0.85rem;
}
/* ===== Animated Lock Icon ===== */
.lock-icon {
display: inline-block;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* ===== Inline Code ===== */
code:not(pre code) {
background: rgba(56,189,248,.12);
color: var(--accent);
padding: 2px 8px;
border-radius: 4px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.88em;
}
/* ===== Numbered Steps ===== */
.step-list {
counter-reset: step;
list-style: none;
padding: 0;
}
.step-list li {
counter-increment: step;
padding: 12px 16px 12px 52px;
position: relative;
margin: 8px 0;
background: var(--card2);
border-radius: 10px;
border: 1px solid var(--border);
font-size: 0.92rem;
color: var(--text2);
}
.step-list li::before {
content: counter(step);
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
width: 26px;
height: 26px;
background: linear-gradient(135deg, var(--accent), var(--accent2));
color: #ffffff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
font-weight: 800;
}
.step-list li strong {
color: var(--text);
}
/* ===== Summary Box ===== */
.summary-box {
background: linear-gradient(135deg, rgba(56,189,248,.1), rgba(129,140,248,.1));
border: 1px solid rgba(56,189,248,.3);
border-radius: 16px;
padding: 32px;
text-align: center;
margin: 32px 0;
}
.summary-box h3 {
font-size: 1.3rem;
background: linear-gradient(135deg, var(--accent), var(--accent2));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 12px;
}
.summary-box p {
color: var(--text2);
max-width: 700px;
margin: 0 auto;
}
.key-formula {
display: inline-block;
background: var(--code-bg);
border: 1px solid var(--border);
padding: 12px 24px;
border-radius: 10px;
font-family: 'JetBrains Mono', monospace;
font-size: 1rem;
margin: 16px 0;
color: var(--accent);
}
/* ===== Tabs (CSS only) ===== */
.tab-group {
margin: 16px 0;
}
.tab-group input[type="radio"] {
display: none;
}
.tab-labels {
display: flex;
gap: 4px;
margin-bottom: 0;
}
.tab-labels label {
padding: 10px 20px;
background: var(--card2);
border: 1px solid var(--border);
border-bottom: none;
border-radius: 10px 10px 0 0;
cursor: pointer;
font-size: 0.9rem;
font-weight: 600;
color: var(--text2);
transition: all 0.2s;
}
.tab-group input[type="radio"]:checked + label {
background: #ffffff;
color: var(--accent);
border-color: var(--accent);
border-bottom-color: #ffffff;
}
.tab-content {
display: none;
background: var(--card);
border: 1px solid var(--border);
border-radius: 0 12px 12px 12px;
padding: 24px;
}
#tab1:checked ~ .tab-content-1,
#tab2:checked ~ .tab-content-2,
#tab3:checked ~ .tab-content-3 {
display: block;
}
/* ===== Scrollbar ===== */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f5f9;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
</style>
</head>
<body>
<!-- ==================== HERO ==================== -->
<header class="hero">
<div class="hero-content">
<h1>🔒 DCL 双重检查锁</h1>
<p class="subtitle">Double-Checked Locking — 高并发下单例模式的经典实现</p>
<div class="badge-row">
<span class="badge blue">并发编程</span>
<span class="badge purple">单例模式</span>
<span class="badge pink">volatile</span>
<span class="badge green">线程安全</span>
</div>
</div>
</header>
<div class="container">
<!-- ==================== 1. 什么是DCL ==================== -->
<section class="section">
<h2 class="section-title">
<span class="icon blue">📖</span>
一、什么是 DCL(双重检查锁)
</h2>
<div class="card">
<p style="font-size: 1.05rem; color: var(--text); margin-bottom: 16px;">
<strong>DCL(Double-Checked Locking)</strong>是一种在多线程环境下实现<strong>延迟初始化(Lazy Initialization)</strong>的优化技术。
它的核心思想是:在获取锁<strong>之前</strong>和<strong>之后</strong>各进行一次检查,从而在保证线程安全的同时,最大限度地减少同步带来的性能开销。
</p>
<div class="summary-box">
<h3>核心公式</h3>
<div class="key-formula">
第1次检查(无锁)→ 加锁 → 第2次检查(有锁)→ 初始化
</div>
<p>两次检查 + 一次加锁 = 线程安全的懒加载</p>
</div>
</div>
<div class="grid-3">
<div class="card">
<h3>🎯 设计目标</h3>
<p>在保证线程安全的前提下,让大多数线程无需获取锁即可直接返回已创建的实例,从而提高性能。</p>
</div>
<div class="card">
<h3>📍 典型场景</h3>
<p>单例模式(Singleton)、延迟加载配置对象、数据库连接池初始化等需要"只创建一次"的场景。</p>
</div>
<div class="card">
<h3>🏗️ 所属领域</h3>
<p>并发编程、Java 内存模型(JMM)、多线程同步机制。最早由 Douglas Schmidt 等人于 1997 年提出。</p>
</div>
</div>
</section>
<!-- ==================== 2. 为什么需要DCL ==================== -->
<section class="section">
<h2 class="section-title">
<span class="icon yellow">⚡</span>
二、为什么需要 DCL?—— 问题背景
</h2>
<p style="color: var(--text2); margin-bottom: 20px;">
在实现单例模式时,我们需要同时满足两个需求:<strong>懒加载</strong>(用到时才创建)和<strong>线程安全</strong>(只创建一个实例)。
下面对比三种实现方案的演进过程:
</p>
<!-- Tab 切换 -->
<div class="tab-group">
<input type="radio" name="tab" id="tab1" checked>
<label for="tab1" class="tab-labels"><label for="tab1">❌ 方案一:不加锁</label></label>
<input type="radio" name="tab" id="tab2">
<label for="tab2" class="tab-labels"><label for="tab2">⚠️ 方案二:全程加锁</label></label>
<input type="radio" name="tab" id="tab3">
<label for="tab3" class="tab-labels"><label for="tab3">✅ 方案三:DCL</label></label>
<div class="tab-content tab-content-1">
<h3 style="color: var(--danger); margin-bottom: 12px;">方案一:不加锁(线程不安全 ❌)</h3>
<div class="code-block">
<div class="code-header">
<div class="dots"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span></div>
<span class="lang">Java</span>
</div>
<pre><code><span class="kw">public class</span> <span class="type">Singleton</span> {
<span class="kw">private static</span> <span class="type">Singleton</span> <span class="field">instance</span>;
<span class="kw">public static</span> <span class="type">Singleton</span> <span class="fn">getInstance</span>() {
<span class="kw">if</span> (<span class="field">instance</span> == <span class="kw">null</span>) { <span class="cm">// 线程A 和 线程B 同时通过检查</span>
<span class="field">instance</span> = <span class="kw">new</span> <span class="type">Singleton</span>(); <span class="cm">// 两个线程各自创建实例 → 单例被破坏!</span>
}
<span class="kw">return</span> <span class="field">instance</span>;
}
}</code></pre>
</div>
<div class="highlight-box danger">
<h4>💥 问题</h4>
<p>当两个线程同时判断 <code>instance == null</code> 为 true 时,会各自创建一个实例,<strong>单例模式被彻底破坏</strong>。</p>
</div>
</div>
<div class="tab-content tab-content-2">
<h3 style="color: var(--accent5); margin-bottom: 12px;">方案二:synchronized 全程加锁(安全但低效 ⚠️)</h3>
<div class="code-block">
<div class="code-header">
<div class="dots"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span></div>
<span class="lang">Java</span>
</div>
<pre><code><span class="kw">public class</span> <span class="type">Singleton</span> {
<span class="kw">private static</span> <span class="type">Singleton</span> <span class="field">instance</span>;
<span class="kw">public static synchronized</span> <span class="type">Singleton</span> <span class="fn">getInstance</span>() {
<span class="kw">if</span> (<span class="field">instance</span> == <span class="kw">null</span>) {
<span class="field">instance</span> = <span class="kw">new</span> <span class="type">Singleton</span>();
}
<span class="kw">return</span> <span class="field">instance</span>;
}
}</code></pre>
</div>
<div class="highlight-box warn">
<h4>⚠️ 问题</h4>
<p>每次调用 <code>getInstance()</code> 都需要获取锁,但实际上<strong>只有第一次调用</strong>才需要同步。
实例创建后,99% 的调用只是读取操作,不应该被锁阻塞。<strong>性能损耗严重</strong>。</p>
</div>
</div>
<div class="tab-content tab-content-3">
<h3 style="color: var(--accent4); margin-bottom: 12px;">方案三:DCL 双重检查锁(安全且高效 ✅)</h3>
<div class="code-block">
<div class="code-header">
<div class="dots"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span></div>
<span class="lang">Java</span>
</div>
<pre><code><span class="kw">public class</span> <span class="type">Singleton</span> {
<span class="kw">private static volatile</span> <span class="type">Singleton</span> <span class="field">instance</span>; <span class="cm">// ① 必须使用 volatile</span>
<span class="kw">public static</span> <span class="type">Singleton</span> <span class="fn">getInstance</span>() {
<span class="kw">if</span> (<span class="field">instance</span> == <span class="kw">null</span>) { <span class="cm">// ② 第一次检查(无锁,快速路径)</span>
<span class="kw">synchronized</span> (<span class="type">Singleton</span>.<span class="kw">class</span>) { <span class="cm">// ③ 加锁</span>
<span class="kw">if</span> (<span class="field">instance</span> == <span class="kw">null</span>) { <span class="cm">// ④ 第二次检查(有锁,防止重复创建)</span>
<span class="field">instance</span> = <span class="kw">new</span> <span class="type">Singleton</span>(); <span class="cm">// ⑤ 创建实例</span>
}
}
}
<span class="kw">return</span> <span class="field">instance</span>;
}
}</code></pre>
</div>
<div class="highlight-box success">
<h4>✅ 优势</h4>
<p>实例创建后,后续所有调用都走<strong>第一次检查直接返回</strong>的快速路径,无需加锁,性能接近无锁方案。</p>
</div>
</div>
</div>
</section>
<!-- ==================== 3. 执行流程 ==================== -->
<section class="section">
<h2 class="section-title">
<span class="icon purple">🔄</span>
三、DCL 执行流程详解
</h2>
<div class="flow-container">
<div class="flow-step">
<span class="step-num">STEP 1</span>
<div class="step-title">线程调用 getInstance()</div>
<div class="step-desc">进入方法</div>
</div>
<div class="flow-arrow"></div>
<div class="flow-step" style="border-color: var(--accent);">
<span class="step-num" style="background: var(--accent);">STEP 2 — 第一次检查</span>
<div class="step-title">if (instance == null) ?</div>
<div class="step-desc">🔓 无锁检查 · 如果 instance 不为 null → 直接返回(快速路径)</div>
</div>
<div class="flow-arrow"></div>
<div class="flow-step" style="border-color: var(--accent2);">
<span class="step-num" style="background: var(--accent2);">STEP 3 — 加锁</span>
<div class="step-title">synchronized (Singleton.class)</div>
<div class="step-desc">🔒 获取内置锁 · 只有 instance 为 null 时才需要加锁</div>
</div>
<div class="flow-arrow"></div>
<div class="flow-step" style="border-color: var(--accent3);">
<span class="step-num" style="background: var(--accent3);">STEP 4 — 第二次检查</span>
<div class="step-title">if (instance == null) ?</div>
<div class="step-desc">🔒 有锁检查 · 防止多个线程排队后重复创建实例</div>
</div>
<div class="flow-arrow"></div>
<div class="flow-step" style="border-color: var(--accent4);">
<span class="step-num" style="background: var(--accent4);">STEP 5 — 创建实例</span>
<div class="step-title">instance = new Singleton()</div>
<div class="step-desc">分配内存 → 初始化对象 → 将引用指向内存</div>
</div>
<div class="flow-arrow"></div>
<div class="flow-step">
<span class="step-num">STEP 6</span>
<div class="step-title">释放锁 & 返回实例</div>
<div class="step-desc">后续线程进入时,第一次检查 instance != null → 直接返回</div>
</div>
</div>
<div class="highlight-box info">
<h4>💡 两次检查各自的作用</h4>
<ul style="margin-top: 8px;">
<li><strong>第一次检查(无锁):</strong>避免实例已创建后,所有线程仍然排队获取锁。这是<strong>性能优化</strong>的关键。</li>
<li><strong>第二次检查(有锁):</strong>当两个线程同时通过第一次检查后,第一个线程创建完实例释放锁,第二个线程进入同步块后需要再次检查,避免<strong>重复创建</strong>。</li>
</ul>
</div>
</section>
<!-- ==================== 4. volatile 的必要性 ==================== -->
<section class="section">
<h2 class="section-title">
<span class="icon red">🔥</span>
四、为什么必须加 volatile?—— 指令重排问题
</h2>
<p style="color: var(--text2); margin-bottom: 20px;">
这是 DCL 中最容易出错、也是最核心的知识点。<code>instance = new Singleton()</code> 在字节码层面并非原子操作,它包含三个步骤:
</p>
<div class="reorder-visual">
<div class="reorder-col wrong">
<h4>❌ 没有 volatile — 可能发生指令重排</h4>
<div class="inst-line">
<span class="line-num">1</span>
<span><strong style="color:var(--accent5)">memory = allocate()</strong> — 分配内存</span>
</div>
<div class="inst-line">
<span class="line-num">2</span>
<span style="color:var(--danger)"><strong>instance = memory</strong> — 引用指向内存 ⚡(提前!)</span>
</div>
<div class="inst-line">
<span class="line-num">3</span>
<span style="color:var(--danger)"><strong>ctor(memory)</strong> — 初始化对象 (被推迟!)</span>
</div>
<div class="reorder-arrow-group">
<span class="arrow-text">⬇️ 2 和 3 可能被重排序 ⬇️</span>
</div>
<div class="highlight-box danger" style="margin: 0;">
<h4>💥 灾难性后果</h4>
<p>线程B 在第一次检查时发现 <code>instance != null</code>(因为步骤2已执行),
但对象<strong>尚未初始化</strong>(步骤3未执行),拿到的是一个<strong>半初始化对象</strong>!</p>
</div>
</div>
<div class="reorder-col right">
<h4>✅ 有 volatile — 禁止指令重排</h4>
<div class="inst-line">
<span class="line-num">1</span>
<span><strong style="color:var(--accent5)">memory = allocate()</strong> — 分配内存</span>
</div>
<div class="inst-line">
<span class="line-num">2</span>
<span><strong style="color:var(--accent4)">ctor(memory)</strong> — 初始化对象</span>
</div>
<div class="inst-line">
<span class="line-num">3</span>
<span><strong style="color:var(--accent4)">instance = memory</strong> — 引用指向内存</span>
</div>
<div class="reorder-arrow-group">
<span class="arrow-text">🔒 volatile 保证顺序执行</span>
</div>
<div class="highlight-box success" style="margin: 0;">
<h4>✅ 安全</h4>
<p>只有对象<strong>完全初始化后</strong>,引用才会被赋值。其他线程看到的要么是 <code>null</code>,要么是<strong>完整初始化的对象</strong>。</p>
</div>
</div>
</div>
<!-- 多线程场景复现 -->
<div class="card" style="margin-top: 24px;">
<h3>🎬 多线程灾难场景复现(无 volatile)</h3>
<div class="thread-diagram">
<div class="thread-row header">
<div></div>
<div class="thread-label t1">线程 A</div>
<div class="thread-label t2">线程 B</div>
</div>
<div class="thread-row">
<div style="font-size:0.8rem; color:var(--text2); text-align:center;">时间1</div>
<div class="thread-action active"><span class="step-tag s1">1</span> 进入 getInstance(),第一次检查 instance == null ✓</div>
<div class="thread-action active"><span class="step-tag s1">1</span> 进入 getInstance(),第一次检查 instance == null ✓</div>
</div>
<div class="thread-row">
<div style="font-size:0.8rem; color:var(--text2); text-align:center;">时间2</div>
<div class="thread-action active"><span class="step-tag s2">2</span> 获取锁,进入 synchronized 块</div>
<div class="thread-action">⏳ 等待获取锁...</div>
</div>
<div class="thread-row">
<div style="font-size:0.8rem; color:var(--text2); text-align:center;">时间3</div>
<div class="thread-action active"><span class="step-tag s3">3</span> 第二次检查 instance == null ✓</div>
<div class="thread-action">⏳ 等待获取锁...</div>
</div>
<div class="thread-row">
<div style="font-size:0.8rem; color:var(--text2); text-align:center;">时间4</div>
<div class="thread-action active"><span class="step-tag s4">4</span> 执行 new:分配内存</div>
<div class="thread-action">⏳ 等待获取锁...</div>
</div>
<div class="thread-row">
<div style="font-size:0.8rem; color:var(--text2); text-align:center;">时间5</div>
<div class="thread-action active"><span class="step-tag s4">4</span> ⚡ 指令重排:instance 指向内存(未初始化!)</div>
<div class="thread-action empty">—</div>
</div>
<div class="thread-row">
<div style="font-size:0.8rem; color:var(--text2); text-align:center;">时间6</div>
<div class="thread-action">释放锁</div>
<div class="thread-action active"><span class="step-tag s2">2</span> 获取锁成功!</div>
</div>
<div class="thread-row">
<div style="font-size:0.8rem; color:var(--text2); text-align:center;">时间7</div>
<div class="thread-action">(对象初始化中...)</div>
<div class="thread-action danger"><span class="step-tag s3">3</span> 第二次检查 instance != null → 跳过创建</div>
</div>
<div class="thread-row">
<div style="font-size:0.8rem; color:var(--text2); text-align:center;">时间8</div>
<div class="thread-action">(对象初始化完成)</div>
<div class="thread-action danger">💥 返回<strong>半初始化</strong>的 instance → 使用崩溃!</div>
</div>
</div>
</div>
<!-- volatile 的两个作用 -->
<div class="grid-2" style="margin-top: 20px;">
<div class="card">
<h3>🛡️ volatile 作用一:禁止指令重排</h3>
<p>通过插入<strong>内存屏障(Memory Barrier)</strong>,确保 <code>new</code> 操作的三个步骤严格按照 分配内存 → 初始化 → 赋值引用 的顺序执行。</p>
</div>
<div class="card">
<h3>👁️ volatile 作用二:保证可见性</h3>
<p>当一个线程修改了 <code>instance</code> 的值,其他线程能<strong>立即看到</strong>最新值,而不是从 CPU 缓存中读取过期的旧值。</p>
</div>
</div>
</section>
<!-- ==================== 5. Java 内存模型 ==================== -->
<section class="section">
<h2 class="section-title">
<span class="icon green">🧠</span>
五、Java 内存模型(JMM)视角
</h2>
<div class="card">
<p style="margin-bottom: 16px;">理解 DCL 离不开对 Java 内存模型的理解。JMM 定义了线程与主内存之间的交互规则:</p>
<div class="memory-visual">
<div class="memory-box">
<h4 style="color: var(--accent);">🧵 线程 A 工作内存</h4>
<div class="mem-item" style="background: rgba(56,189,248,.1); border: 1px solid rgba(56,189,248,.2);">instance 的副本</div>
<div class="mem-item" style="background: rgba(56,189,248,.1); border: 1px solid rgba(56,189,248,.2);">其他变量...</div>
</div>
<div class="memory-connector">⇄</div>
<div class="memory-box" style="border-color: var(--accent5);">
<h4 style="color: var(--accent5);">💾 主内存(共享)</h4>
<div class="mem-item" style="background: rgba(251,191,36,.1); border: 1px solid rgba(251,191,36,.2);">instance(真实值)</div>
<div class="mem-item" style="background: rgba(251,191,36,.1); border: 1px solid rgba(251,191,36,.2);">其他共享变量...</div>
</div>
<div class="memory-connector">⇄</div>
<div class="memory-box">
<h4 style="color: var(--accent3);">🧵 线程 B 工作内存</h4>
<div class="mem-item" style="background: rgba(244,114,182,.1); border: 1px solid rgba(244,114,182,.2);">instance 的副本</div>
<div class="mem-item" style="background: rgba(244,114,182,.1); border: 1px solid rgba(244,114,182,.2);">其他变量...</div>
</div>
</div>
<div class="highlight-box info">
<h4>🔑 volatile 在 JMM 中的语义</h4>
<ul style="margin-top: 8px;">
<li><strong>写 volatile 变量时:</strong>JMM 会把该线程工作内存中的值<strong>刷新到主内存</strong>,并在写操作前插入 <strong>StoreStore</strong> + <strong>StoreLoad</strong> 屏障。</li>
<li><strong>读 volatile 变量时:</strong>JMM 会把该线程工作内存中的值<strong>置为无效</strong>,必须从主内存重新读取,并在读操作后插入 <strong>LoadLoad</strong> + <strong>LoadStore</strong> 屏障。</li>
</ul>
</div>
</div>
</section>
<!-- ==================== 6. 完整代码 ==================== -->
<section class="section">
<h2 class="section-title">
<span class="icon blue">💻</span>
六、完整代码实现
</h2>
<div class="code-block">
<div class="code-header">
<div class="dots"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span></div>
<span class="lang">Singleton.java — 完整 DCL 实现</span>
</div>
<pre><code><span class="kw">public final class</span> <span class="type">Singleton</span> {
<span class="cm">/**
* volatile 关键字是必须的!
* 1. 禁止指令重排序(防止返回半初始化对象)
* 2. 保证多线程间的可见性(一个线程写入后其他线程立即可见)
*/</span>
<span class="kw">private static volatile</span> <span class="type">Singleton</span> <span class="field">instance</span>;
<span class="cm">// 私有构造函数,防止外部 new</span>
<span class="kw">private</span> <span class="fn">Singleton</span>() {
<span class="cm">// 防止反射攻击(可选)</span>
<span class="kw">if</span> (<span class="field">instance</span> != <span class="kw">null</span>) {
<span class="kw">throw new</span> <span class="type">RuntimeException</span>(<span class="str">"Use getInstance() to create."</span>);
}
}
<span class="kw">public static</span> <span class="type">Singleton</span> <span class="fn">getInstance</span>() {
<span class="cm">// 第一次检查:无锁快速路径</span>
<span class="cm">// 大多数调用在这里直接返回,性能最优</span>
<span class="kw">if</span> (<span class="field">instance</span> == <span class="kw">null</span>) {
<span class="cm">// 加锁:只有第一次创建时才会进入</span>
<span class="kw">synchronized</span> (<span class="type">Singleton</span>.<span class="kw">class</span>) {
<span class="cm">// 第二次检查:防止重复创建</span>
<span class="cm">// 场景:线程A和B同时通过第一次检查</span>
<span class="cm">// 线程A先获取锁创建实例</span>
<span class="cm">// 线程B进入后必须再次检查</span>
<span class="kw">if</span> (<span class="field">instance</span> == <span class="kw">null</span>) {
<span class="field">instance</span> = <span class="kw">new</span> <span class="type">Singleton</span>();
}
}
}
<span class="kw">return</span> <span class="field">instance</span>;
}
<span class="cm">// 防止序列化破坏单例(可选)</span>
<span class="kw">protected</span> <span class="type">Object</span> <span class="fn">readResolve</span>() {
<span class="kw">return</span> <span class="fn">getInstance</span>();
}
}</code></pre>
</div>
</section>
<!-- ==================== 7. 替代方案 ==================== -->
<section class="section">
<h2 class="section-title">
<span class="icon purple">🔀</span>
七、DCL 的替代方案
</h2>
<div class="grid-2">
<div class="card">
<h3>📦 静态内部类(推荐)</h3>
<div class="code-block">
<div class="code-header">
<div class="dots"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span></div>
<span class="lang">Java</span>
</div>
<pre><code><span class="kw">public class</span> <span class="type">Singleton</span> {
<span class="kw">private</span> <span class="fn">Singleton</span>() {}
<span class="cm">// 利用类加载机制保证线程安全</span>
<span class="kw">private static class</span> <span class="type">Holder</span> {
<span class="kw">static final</span> <span class="type">Singleton</span> <span class="field">INSTANCE</span>
= <span class="kw">new</span> <span class="type">Singleton</span>();
}
<span class="kw">public static</span> <span class="type">Singleton</span> <span class="fn">getInstance</span>() {
<span class="kw">return</span> <span class="type">Holder</span>.<span class="field">INSTANCE</span>;
}
}</code></pre>
</div>
<p style="margin-top: 8px;">✅ 利用 JVM 类加载机制保证线程安全,代码更简洁,无需 volatile。</p>
</div>
<div class="card">
<h3>🔢 枚举(最安全)</h3>
<div class="code-block">
<div class="code-header">
<div class="dots"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span></div>
<span class="lang">Java</span>
</div>
<pre><code><span class="kw">public enum</span> <span class="type">Singleton</span> {
<span class="field">INSTANCE</span>;
<span class="cm">// 业务方法</span>
<span class="kw">public void</span> <span class="fn">doSomething</span>() {
<span class="cm">// ...</span>
}
}</code></pre>
</div>
<p style="margin-top: 8px;">✅ 天然线程安全,防反射、防序列化破坏。Joshua Bloch 推荐。</p>
</div>
</div>
<table class="styled-table" style="margin-top: 24px;">
<thead>
<tr>
<th>方案</th>
<th>线程安全</th>
<th>懒加载</th>
<th>防反射</th>
<th>防序列化</th>
<th>复杂度</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>DCL 双重检查锁</strong></td>
<td>✅ (需 volatile)</td>
<td>✅</td>
<td>⚠️ 需额外处理</td>
<td>⚠️ 需 readResolve</td>
<td>中等</td>
</tr>
<tr>
<td><strong>静态内部类</strong></td>
<td>✅</td>
<td>✅</td>
<td>⚠️ 需额外处理</td>
<td>⚠️ 需 readResolve</td>
<td>低</td>
</tr>
<tr>
<td><strong>枚举</strong></td>
<td>✅</td>
<td>❌ (类加载即创建)</td>
<td>✅</td>
<td>✅</td>
<td>最低</td>
</tr>
<tr>
<td><strong>饿汉式</strong></td>
<td>✅</td>
<td>❌</td>
<td>⚠️ 需额外处理</td>
<td>⚠️ 需 readResolve</td>
<td>最低</td>
</tr>
</tbody>
</table>
</section>
<!-- ==================== 8. 优缺点 ==================== -->
<section class="section">
<h2 class="section-title">
<span class="icon green">⚖️</span>
八、DCL 优缺点分析
</h2>
<div class="pros-cons">
<div class="pros-card">
<h3>✅ 优点</h3>
<ul>
<li><strong>高性能</strong>:实例创建后,后续调用无需加锁,走无锁快速路径</li>
<li><strong>懒加载</strong>:只在第一次使用时才创建实例,节省资源</li>
<li><strong>线程安全</strong>:正确实现后完全保证线程安全</li>
<li><strong>广泛使用</strong>:是经典并发模式,被大量框架和库采用</li>
<li><strong>粒度可控</strong>:锁的范围精确到初始化阶段</li>
</ul>
</div>
<div class="cons-card">
<h3>⚠️ 缺点 / 注意事项</h3>
<ul>
<li><strong>实现复杂</strong>:必须使用 volatile,遗漏则产生严重 bug</li>
<li><strong>理解门槛高</strong>:涉及 JMM、指令重排、内存屏障等底层概念</li>
<li><strong>反射/序列化风险</strong>:需额外代码防护</li>
<li><strong>不适用于所有场景</strong>:如果初始化不昂贵,饿汉式更简单</li>
<li><strong>调试困难</strong>:指令重排导致的问题难以复现和定位</li>
</ul>
</div>
</div>
</section>
<!-- ==================== 9. 常见错误 ==================== -->
<section class="section">
<h2 class="section-title">
<span class="icon red">🚨</span>
九、常见错误与陷阱
</h2>
<div class="timeline">
<div class="timeline-item">
<h4 style="color: var(--danger);">陷阱 1:忘记 volatile</h4>
<p>最常见的错误。没有 volatile,<code>new</code> 操作可能被重排序,导致其他线程获取到半初始化对象。这种 bug 在测试中极难复现,可能只在高并发生产环境下偶尔出现。</p>
</div>
<div class="timeline-item">
<h4 style="color: var(--danger);">陷阱 2:省略第二次检查</h4>
<p>如果去掉 synchronized 块内的第二次 <code>if (instance == null)</code> 检查,当两个线程同时通过第一次检查后,会<strong>各自创建</strong>一个实例,单例被破坏。</p>
</div>
<div class="timeline-item">
<h4 style="color: var(--danger);">陷阱 3:锁对象不一致</h4>
<p>如果 synchronized 锁的不是同一个对象(例如锁了 <code>this</code> 而不是 <code>Class</code>),不同线程可能进入不同的同步块,失去互斥效果。</p>
</div>
<div class="timeline-item">
<h4 style="color: var(--accent5);">陷阱 4:构造函数中抛出异常</h4>
<p>如果构造函数抛出异常,<code>instance</code> 可能已被赋值(在 volatile 写之前),但对象未完全初始化。后续线程看到 instance 不为 null,直接使用了一个<strong>异常状态</strong>的对象。</p>
</div>
<div class="timeline-item">
<h4 style="color: var(--accent5);">陷阱 5:在 C/C++ 中套用 DCL</h4>
<p>DCL 在 Java 5+ 中因 volatile 语义增强才正确。在 C/C++ 中,需要使用 <code>std::atomic</code> 和 <code>memory_order</code> 来保证类似的语义,不能直接照搬 Java 写法。</p>
</div>
</div>
</section>
<!-- ==================== 10. 总结 ==================== -->
<section class="section">
<h2 class="section-title">
<span class="icon blue">📋</span>
十、总结
</h2>
<div class="summary-box">
<h3>🔑 DCL 核心要点速记</h3>
<div style="text-align: left; max-width: 650px; margin: 20px auto;">
<ol class="step-list">
<li><strong>volatile 不可省略</strong> — 禁止指令重排 + 保证可见性,是 DCL 正确性的基石</li>
<li><strong>两次检查缺一不可</strong> — 第一次检查优化性能(无锁快速路径),第二次检查保证安全(防重复创建)</li>
<li><strong>synchronized 锁 Class</strong> — 确保所有线程竞争同一把锁</li>
<li><strong>new 操作非原子</strong> — 分配内存 → 初始化 → 赋值引用,三步可能被重排</li>
<li><strong>考虑替代方案</strong> — 静态内部类和枚举通常更简单、更安全</li>
</ol>
</div>
</div>
<div class="vs-container">
<div class="vs-card bad">
<h3>❌ 错误写法</h3>
<ul>
<li>没有 volatile 修饰 instance</li>
<li>只有第一次检查,没有第二次</li>
<li>synchronized 锁了 this 而非 Class</li>
<li>用 DCL 初始化非单例资源</li>
<li>在 C/C++ 中直接照搬 Java 写法</li>
</ul>
</div>
<div class="vs-divider">VS</div>
<div class="vs-card good">
<h3>✅ 正确写法</h3>
<ul>
<li>instance 用 volatile 修饰</li>
<li>两次 null 检查,一次无锁一次有锁</li>
<li>synchronized 锁 Class 对象</li>
<li>构造函数不抛异常</li>
<li>必要时加 readResolve 防序列化</li>
</ul>
</div>
</div>
</section>
</div>
<footer class="footer">
<p>DCL (Double-Checked Locking) 详解 · 并发编程核心模式</p>
<p style="margin-top: 4px;">参考:《Java 并发编程的艺术》· 《Java 内存模型规范》· Brian Goetz "Java Concurrency in Practice"</p>
</footer>
</body>
</html>