Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
258: StampedLock for Optimistic Reads
Imagine you're in a busy office and there's a whiteboard that displays the current project deadline. Most of the time, you just need to glance at it to make sure you're on track. Now, you could be really cautious: every time you look at the board, you put a "Do Not Erase" sticky note over the date. That's a traditional read lock. It works, but it's annoying because if the project manager needs to change the date, they have to wait for every single person to finish their glance and remove their sticky notes.
Instead, imagine you just glance at the board. While you're reading the date, you notice a small version number in the corner—let's say it's "v4". You read the date, and then you check the version number again. If it's still "v4", you're golden. But if it's now "v5", it means someone changed the date while you were looking at it, and your data might be a weird mix of the old and new dates. In that case, you don't panic; you just put the sticky note on the board and read it properly this time.
That's exactly how StampedLock works with optimistic reads. It's designed for those scenarios where reads are incredibly frequent and writes are rare, and you want to avoid the overhead of a full-blown lock whenever possible.
The Overhead of Traditional Read-Write Locks
You've already seen ReentrantReadWriteLock. It's great, but it has a cost. Even a read lock has to update the lock's internal state to keep track of how many readers are active. In a high-concurrency environment, that state update becomes a bottleneck because multiple threads are fighting to update the same counter. I've seen this kill performance in low-latency trading apps where the read-to-write ratio is 1000:1.
StampedLock solves this by providing a "stamp"—a long value—that represents a version of the lock state. An optimistic read doesn't actually lock anything; it just asks for the current stamp and hopes for the best.
The 'Hope for the Best' Implementation
Let's look at a real example. Suppose we're tracking a player's position in a game world. We have X and Y coordinates that always need to be updated together.
public class PlayerPosition {
private double x, y;
private final StampedLock sl = new StampedLock();
public void move(double deltaX, double deltaY) {
long stamp = sl.writeLock(); // This is a pessimistic write lock
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
public double[] getPosition() {
// 1. Try an optimistic read
long stamp = sl.tryOptimisticRead();
// 2. Copy the shared state into local variables
double currentX = x;
double currentY = y;
// 3. Validate the stamp
if (!sl.validate(stamp)) {
// The state changed! Fall back to a pessimistic read lock
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return new double[]{currentX, currentY};
}
}
Mapping the Logic to the Code
Notice the specific sequence in getPosition(). This is the pattern you'll use every time you implement an optimistic read:
- The Glance:
tryOptimisticRead()doesn't block anything. It just grabs the current version of the lock. If a writer is currently holding the lock, this will actually return 0 immediately. - The Local Copy: I read
xandyinto local variables. This is crucial. If you read the variables directly into a return statement or a complex calculation, you're exposing yourself to "torn reads" (wherexis from the old version andyis from the new version). - The Validation:
sl.validate(stamp)checks if a write lock was acquired since we got our stamp. If it returns false, our local copies are potentially corrupted. - The Fallback: If validation fails, we don't just give up. We switch to a
readLock(). This blocks any writers until we've successfully copied the data, ensuring we get a consistent snapshot.
One thing to keep in mind: StampedLock is not reentrant. If you call a method that acquires a write lock from within another method that already holds a write lock on the same StampedLock instance, you'll deadlock yourself. It's a bit more "primitive" than ReentrantReadWriteLock, but that's why it's so much faster.
📋 Practical Task
Build a Thread-Safe High-Score Tracker with StampedLock
You are building a leaderboard for a competitive game. You need to create a class called HighScoreTracker that tracks the topScore (an integer) and the topPlayerName (a String). Since thousands of players will be checking the high score every second, but the score only changes occasionally, this is a perfect use case for StampedLock.
Requirements:
- Implement a method
updateScore(String name, int score)that updates both the name and the score only if the new score is higher than the currenttopScore. Use a pessimistic write lock for this. - Implement a method
getHighScoreDetails()that returns a record or a small POJO containing both the name and the score. - In
getHighScoreDetails(), you must implement the optimistic read pattern:- Attempt an optimistic read.
- Copy the name and score to local variables.
- Validate the stamp.
- Fall back to a pessimistic read lock if validation fails.
Testing your implementation: Create a main method where one thread constantly updates the score with random values, while five other threads constantly read the high score. Verify that the name and score always match (i.e., you never see a new score paired with an old player's name).
There are no comments for now.