1552 lines
60 KiB
HTML
1552 lines
60 KiB
HTML
<!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> |