The Problem: Single-Threaded Blocking 🎭 Analogy: A Restaurant Kitchen Imagine your game is a restaurant kitchen with only ONE chef (the Event Dispatch Thread - EDT): Before the fix (blocking):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Chef (EDT) Timeline: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Time: 0s 1s 2s 3s 4s 5s │ │ │ │ │ │ Collision happens! 💥 │ ├─ 🔊 Play explosion sound (2 seconds) │ ┌─────────────────────┐ │ │ Playing audio... │ ← Chef is BUSY │ │ Can't do anything │ making food! │ │ else! │ │ └─────────────────────┘ │ │ ┌───────────────┐ │ 🎨 Paint │ ← Customer waits! │ explosion │ Food arrives late! └───────────────┘
Root Cause Analysis:
Audio plays in constructor on EDT: The new Audio("audio/explode.wav").play() call in the Explode constructor runs on the Event Dispatch Thread (EDT), blocking the paint operations. 单线程阻塞
GIF animation not properly handled: You’re loading GIF frames as static BufferedImages, but GIFs need special handling to display their animation frames properly.
Step increment without bounds checking: In Explode.paint(), the step++ increments every time paint is called, but paint might be called multiple times per frame cycle.
No preloading of explosion images: Although images are loaded in ResourceMgr’s static block, GIF frames need to be extracted properly.
其他的原因我们暂不考虑, 只聚焦线程阻塞问题。
Before (blocking version)
1 2 3 4 5 6 7
// In Explode constructor - called from paint() method publicExplode(int x, int y, TankFrame tf){ this.x = x; this.y = y; this.tf = tf; new Audio("audio/explode.wav").play(); // ⚠️ BLOCKS HERE! }
long currentTime = System.currentTimeMillis(); if (currentTime - lastUpdate >= FRAME_DELAY) { g.drawImage(ResourceMgr.explosions[step], x, y, null); lastUpdate = currentTime; step++; } else { if (step > 0) { g.drawImage(ResourceMgr.explosions[step - 1], x, y, null); } } } }
After (Non-Blocking version):
1 2 3 4 5 6 7
// In Explode constructor publicExplode(int x, int y, TankFrame tf){ this.x = x; this.y = y; this.tf = tf; new Thread(() -> new Audio("audio/explode.wav").play()).start(); // ✨ NEW THREAD! }
Main Game Loop(T.java line 19-22) ↓ calls repaint() every 50ms ↓ TankFrame.paint()(line 92) ↓ draws everything ↓ Bullet.collidesWith()(line 97) ↓ detects collision at line 106 ↓ new Explode() at line 111 ↓ Explode constructor(line 15) ↓ new Thread().start() ╔═══════════════════════════╗ ↗ New thread created! ║ Thread runs separately ║ ↗ Doesn't block EDT! ║ Audio plays in parallel ║ ↗ ╚═══════════════════════════╝ ↗ ↓ (returns IMMEDIATELY!) paint() continues immediately ✅ ↓ next frame renders in 50ms! 🎉
Timeline:
1 2 3 4 5 6 7 8 9 10 11
Main Thread(EDT): Frame 1 (t=0ms): Collision → Create Explode → Start audio thread → Paint explosion ✅ Frame 2 (t=50ms): Paint next animation frame ✅ Frame 3 (t=100ms): Paint next animation frame ✅ Frame 4 (t=150ms): Paint next animation frame ✅ Audio Thread(separate): t=0ms: Start playing audio 🔊 t=50ms: Still playing... 🔊 t=100ms: Still playing... 🔊 t=1500ms: Audio finishes 🔊
🎬 Animation: Thread Execution Flow
Single Thread (Blocking) - Like a Single-Lane Road:
SCENARIO 1: Single-threaded phone(old phones) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Open music app → Play song - Try to open camera → WAIT! Music must finish first! ❌ - Try to check messages → WAIT! Music must finish first! ❌ SCENARIO 2: Multi-threaded phone(modern phones) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Open music app → Play song in background ✅ - Open camera → Works fine! (separate thread) ✅ - Check messages → Works fine! (separate thread) ✅ Your game is the same! - EDT = Main UI thread(handles painting, input) - Audio thread = Background worker(handles sound)
📈 Performance Comparison
🎯 Summary The Root Cause:
Java’s paint() method runs on the Event Dispatch Thread (EDT)
The EDT is single-threaded - it does ONE thing at a time
Audio.play() has a long-running loop (1-2 seconds)
When you call play() directly in the constructor, the EDT stops painting to play audio
Result: You hear sound but see nothing until audio finishes!
The Solution:
Create a new thread specifically for audio
The new thread runs independently from the EDT
EDT can continue painting while audio plays in parallel
Result: Sound and visuals happen simultaneously! 🎉