Designing a Thread-Safe Game Leaderboard Using ConcurrentSkipListMap

Welcome back! In our previous lesson, we explored how to implement a concurrent inventory system using ConcurrentHashMap. Today, we will take your skills further by designing a real-time, thread-safe game leaderboard using ConcurrentSkipListMap. This lesson will help you understand how to manage sorted data in a concurrent environment—an essential feature for real-time applications such as online games or financial systems.

What You'll Learn

By the end of this lesson, you will:

  • Understand how ConcurrentSkipListMap maintains sorted data in a thread-safe manner.
  • Learn how to update entries safely while handling concurrent modifications.
  • Retrieve top-scoring entries efficiently, which is crucial for leaderboard management.

These skills will ensure you are well-equipped to handle concurrent data access challenges in applications that require real-time data sorting and updates.

Let’s dive in and start building our concurrent game leaderboard!

Recap: Understanding ConcurrentSkipListMap

Before we start implementing the leaderboard, let's quickly recap ConcurrentSkipListMap and why it's well-suited for our use case.

ConcurrentSkipListMap is part of Java's java.util.concurrent package and is a thread-safe variant of TreeMap. It maintains keys in a sorted order and allows efficient retrieval of data in real time. One of the primary reasons to use ConcurrentSkipListMap is that it provides thread-safe access to the elements while ensuring that the entries are always sorted by key.

This makes it ideal for use cases like leaderboards, where you want to maintain a ranking of players by score and allow concurrent updates.

  • Thread Safety: Unlike traditional collections, ConcurrentSkipListMap allows multiple threads to update the map without the need for explicit synchronization or locking.
  • Sorted Order: The map automatically sorts entries based on keys (in our case, player scores), making it easy to retrieve the highest-scoring players.

However, by default, ConcurrentSkipListMap sorts the keys in ascending order. Since we want the leaderboard to rank players with the highest scores first, we need to modify the natural ordering.

Adjusting the Sorting Order with reverseOrder()

To ensure that the highest scores come first, we use Collections.reverseOrder() when initializing ConcurrentSkipListMap. This reverses the natural ordering of the keys.

  • Why Use reverseOrder(): By passing Collections.reverseOrder() to the constructor, we tell the map to sort the keys in descending order. This way, when we iterate over the map, we start with the highest scores.
Creating the Game Leaderboard

We'll begin by constructing the Leaderboard class, which maintains player scores and ensures they are always sorted with the highest scores first.

This class uses two concurrent maps:

  • ConcurrentSkipListMap (scoreMap): Stores scores mapped to sets of player names, ensuring the scores are always in descending order.
  • ConcurrentHashMap (playerScores): Quickly retrieves a player’s current score, ensuring we can efficiently update a player's score.
Adding and Updating Scores

Let’s implement the addScore() method, which will add or update a player’s score in the leaderboard:

  • playerScores.put(): Stores the player's current score in playerScores. If the player already had a score (oldScore is not null), we remove the old score from scoreMap.

  • Removing Old Score:

    • computeIfPresent(): Checks if the old score exists in scoreMap. If it does, it removes the player from the set of players with that old score.
    • Cleanup: If no players are left for that score, the score is removed from the map to prevent memory leaks.
  • Adding New Score:

    • compute(): Adds the player to the new score in scoreMap. If the score isn’t already present, a new set is created, and the player is added.
    • Thread Safety: Using compute() ensures atomic updates to the map, preventing race conditions.

This method ensures that each player has only one score entry, and the leaderboard remains sorted by score at all times.

Getting the Top Players

Next, let's implement the getTopNPlayers() method, which retrieves the top n players from the leaderboard. This method is crucial because it allows us to efficiently extract the highest-scoring players based on their scores, leveraging the built-in sorting mechanism of ConcurrentSkipListMap.

Here’s how it works:

  • Iterating Over the scoreMap: The ConcurrentSkipListMap is already sorted in descending order due to reverseOrder(). This allows us to start from the highest score and work our way down. We use the entrySet() to iterate over the scores and the corresponding players.

  • Fetching Players for Each Score: Since the scoreMap stores scores as keys and sets of players as values, we loop through the set of players for each score. Each player is then added to our topPlayers list.

  • Limiting to n Players: To ensure we only retrieve the top n players, we check the size of the topPlayers list in each iteration. As soon as the list reaches the size n, we stop the process and return the result.

This method efficiently fetches the top players in real time by leveraging the sorted order of the ConcurrentSkipListMap. As scores are updated, this method can quickly return the current highest-ranked players without additional sorting operations.

The design ensures thread safety as well. The ConcurrentSkipListMap allows concurrent reads and updates without external synchronization, making this method suitable for highly concurrent environments where multiple threads update player scores.

This approach is scalable, allowing you to retrieve top players even in large leaderboards without performance concerns, as the ConcurrentSkipListMap keeps scores sorted at all times.

Displaying the Leaderboard

Finally, we implement the displayLeaderboard() method to print the entire leaderboard in order of scores:

This method uses the forEach method to print each player and their score, maintaining the leaderboard’s sorted order.

  • Order Maintained: Due to the reverseOrder() in scoreMap, players are displayed from the highest score to the lowest.
Final Implementation

Here’s the full implementation of the Leaderboard class, incorporating all the methods and ensuring the highest scores are ranked first:

Simulating Concurrent Updates

Now, let’s simulate multiple threads updating the leaderboard concurrently.

This Main class simulates multiple threads updating the leaderboard simultaneously. Each thread represents a different group of players adding or updating their scores.

  • Thread Coordination: We use join() to ensure all threads complete their updates before the final leaderboard is displayed.
  • Concurrency: Thanks to ConcurrentSkipListMap and ConcurrentHashMap, updates are handled in a thread-safe way without explicit locking.
Why It Matters

Implementing a real-time, thread-safe leaderboard is essential for many applications, such as games or any competitive environment where rankings are critical. Here's why this matters:

  • Real-Time Data Handling: The ConcurrentSkipListMap ensures that all updates and reads happen in a thread-safe and efficient manner.

  • Data Consistency: By maintaining a sorted structure, we can easily retrieve the top-performing players in real time.

  • Concurrency: Using concurrent collections like ConcurrentSkipListMap allows multiple threads to update the leaderboard without blocking each other, providing smooth and scalable performance.

Incorporating these techniques into your applications will give you the skills to manage real-time data and concurrent updates efficiently.

Now, let’s move on to the practice section and apply these concepts to more advanced challenges!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal