Implementing Custom Rate Limiting in Java with ReentrantLock and AtomicInteger

This article explains the purpose and benefits of rate limiting, reviews popular Java rate‑limiting libraries, and provides a step‑by‑step guide with complete source code for building a simple, thread‑safe custom rate limiter using maps, ReentrantLock, and AtomicInteger.

FunTester
FunTester
FunTester
Implementing Custom Rate Limiting in Java with ReentrantLock and AtomicInteger

Rate limiting is a technique used in service programs to restrict the request rate of a particular class of requests, preventing excessive consumption of server resources and ensuring service stability.

The main benefits of rate limiting include protecting system stability, preventing abuse and malicious attacks such as DoS, controlling resource consumption, and improving overall service quality.

Protect system stability: Limits prevent overload and performance degradation.

Prevent abuse and attacks: Reduces risk of DoS, brute‑force, and other malicious activities.

Control resource consumption: Ensures limited resources like databases are not over‑queried.

Improve service quality: Decreases queueing time and provides fairer resource distribution.

Common Java frameworks for rate limiting include Guava RateLimiter, Resilience4j, Alibaba Sentinel, Netflix Hystrix, and Bucket4j. While powerful, these frameworks may be overkill for simple scenarios where only basic request throttling is needed.

Approach

The custom implementation uses a Map<String, LimitConfig> to store per‑key configurations (maximum count and time window). Three additional Map<String, Integer> structures track the last request timestamp, request count, and a lock object for each key. Thread safety is ensured with a global ReentrantLock for configuration changes and per‑key ReentrantLock instances for request handling, while request counts are stored in java.util.concurrent.atomic.AtomicInteger objects.

Code

Below is the complete source code of the custom rate limiter.

import com.funtester.frame.SourceCode
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantLock

/**
 * Rate limiting utility supporting N/M throttling
 */
class RateLimit {
    /** total rate‑limit configuration */
    Map<String, LimitConfig> config = [:]
    /** last request timestamp */
    Map<String, Integer> lastTime = [:]
    /** request count */
    Map<String, AtomicInteger> requestTimes = [:]
    /** lock for each key */
    Map<String, ReentrantLock> allLock = [:]
    /** write lock for configuration updates */
    ReentrantLock writeLock = new ReentrantLock()

    /**
     * Determines whether the given key is currently limited.
     */
    boolean isLimit(String key) {
        if (!config.containsKey(key)) {
            addConfig(key, 2, 2) // default configuration
            return isLimit(key) // recursive initialization
        }
        def mark = SourceCode.getMark()
        if (mark - lastTime[key] >= config[key].duration) { // new time window
            if (allLock[key].tryLock(1, TimeUnit.SECONDS)) {
                if (mark - lastTime[key] >= config[key].duration) {
                    lastTime[key] = mark
                    requestTimes[key] = new AtomicInteger(1)
                    allLock[key].unlock()
                    return false
                } else {
                    return true
                }
            }
        }
        if (requestTimes[key].get() >= config[key].maxTimes) {
            return true // exceeded max count
        }
        requestTimes[key].getAndIncrement()
        return false
    }

    /**
     * Adds a rate‑limit configuration for a specific key.
     */
    def addConfig(String key, int maxTimes, int duration) {
        if (writeLock.tryLock(1, TimeUnit.SECONDS)) {
            try {
                if (!config.containsKey(key)) {
                    config[key] = new LimitConfig(maxTimes: maxTimes, duration: duration)
                    allLock[key] = new ReentrantLock()
                    lastTime[key] = SourceCode.getMark()
                    requestTimes[key] = new AtomicInteger(0)
                }
            } finally {
                writeLock.unlock()
            }
        }
    }

    /**
     * Rate‑limit configuration class.
     */
    static class LimitConfig {
        /** maximum allowed requests */
        int maxTimes
        /** time window in seconds */
        int duration
    }
}

Test

The following script demonstrates how to use the custom limiter.

import com.funtester.httpclient.FunHttp
import com.funtester.utils.RateLimit

class Routine extends FunHttp {
    static void main(String[] args) {
        def limit = new RateLimit()
        limit.addConfig("test", 1, 1)
        1000.times {
            sleep(0.1)
            fun {
                def limit1 = limit.isLimit("t4est")
                if (!limit1) {
                    output("未限流")
                }
            }
        }
    }
}

Console output shows that the default 2 requests per 2 seconds configuration takes effect.

2021 Original Collection 2022 Original Collection 2023 Original Collection API Function Test Series Performance Test Series Java, Groovy, Go, Python
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaconcurrencyrate limitingToken Bucket
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.