DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Generics in Java and Their Implementation
  • Multithreading in Modern Java: Advanced Benefits and Best Practices
  • Apache Spark 3 to Apache Spark 4 Migration: What Breaks, What Improves, What's Mandatory
  • Memory Optimization and Utilization in Java 25 LTS: Practical Best Practices

Trending

  • Why Your DLP Policies Fall Short the Moment AI Agents Enter the Picture
  • What Is Plagiarism? How to Avoid It and Cite Sources
  • DevOps and Platform Engineering Readiness Checklist: Everything Needed for a Scalable, Secure, High-Velocity Delivery Platform
  • Stop Running Two Data Systems for One Agent Query
  1. DZone
  2. Coding
  3. Java
  4. Exploring Throttling in Java: Simple Implementation Examples - Part 1

Exploring Throttling in Java: Simple Implementation Examples - Part 1

Effectively managing resource consumption and ensuring fair usage of services are vital considerations for building scalable and robust applications.

By 
Andrei Tuchin user avatar
Andrei Tuchin
DZone Core CORE ·
Dec. 31, 23 · Tutorial
Likes (13)
Comment
Save
Tweet
Share
14.9K Views

Join the DZone community and get the full member experience.

Join For Free

In the world of software development, effectively managing resource consumption and ensuring fair usage of services are vital considerations for building scalable and robust applications. Throttling, the practice of controlling the rate at which certain operations are performed, emerges as a crucial mechanism for achieving these objectives. In this article, we'll delve into various ways to implement throttling in Java, presenting diverse strategies with practical examples.

Disclaimer: In this article, I focus on uncomplicated single-threaded illustrations to address fundamental scenarios. 

Understanding Throttling

Throttling involves regulating the frequency at which certain actions are allowed to occur. This is particularly important in scenarios where the system needs protection from abuse, demands resource management, or requires fair access to shared services. Common use cases for throttling include rate-limiting API requests, managing data updates, and controlling access to critical resources.

Simple Blocking Rate Limiter With thread.sleep() - Not Use in Production!

A straightforward approach to implement throttling is by using the Thread.sleep() method to introduce delays between consecutive operations. While this method is simple, it may not be suitable for high-performance scenarios due to its blocking nature. 

Java
 
public class SimpleRateLimiter {

    private long lastExecutionTime = 0;
    private long intervalInMillis;

    public SimpleRateLimiter(long requestsPerSecond) {
        this.intervalInMillis = 1000 / requestsPerSecond;
    }

    public void throttle() throws InterruptedException {
        long currentTime = System.currentTimeMillis();
        long elapsedTime = currentTime - lastExecutionTime;

        if (elapsedTime < intervalInMillis) {
            Thread.sleep(intervalInMillis - elapsedTime);
        }

        lastExecutionTime = System.currentTimeMillis();
        // Perform the throttled operation
        System.out.println("Throttled operation executed at: " + lastExecutionTime);
    }
}


In this example, the SimpleRateLimiter class allows a specified number of operations per second. If the time elapsed between operations is less than the configured interval, it introduces a sleep duration to achieve the desired rate. 

Basic Throttling with wait

Let's start with a simple example that we use wait to throttle the execution of a method. The goal is to allow the method to be invoked only after a certain cooldown period has elapsed. 

Java
 
public class BasicThrottling {

    private final Object lock = new Object();
    private long lastExecutionTime = 0;
    private final long cooldownMillis = 5000; // 5 seconds cooldown

    public void throttledOperation() throws InterruptedException {
        synchronized (lock) {
            long currentTime = System.currentTimeMillis();
            long elapsedTime = currentTime - lastExecutionTime;

            if (elapsedTime < cooldownMillis) {
                lock.wait(cooldownMillis - elapsedTime);
            }

            lastExecutionTime = System.currentTimeMillis();
            // Perform the throttled operation
            System.out.println("Throttled operation executed at: " + lastExecutionTime);
        }
    }
}


In this example, the throttledOperation method uses the wait method to make the thread wait until the cooldown period elapses. 

Dynamic Throttling With Wait and Notify

Let's enhance the previous example to introduce dynamic throttling, where the cooldown period can be adjusted dynamically. Production must have an opportunity to make a change in flight.

Java
 
public class DynamicThrottling {

    private final Object lock = new Object();
    private long lastExecutionTime = 0;
    private long cooldownMillis = 5000; // Initial cooldown: 5 seconds

    public void throttledOperation() throws InterruptedException {
        synchronized (lock) {
            long currentTime = System.currentTimeMillis();
            long elapsedTime = currentTime - lastExecutionTime;

            if (elapsedTime < cooldownMillis) {
                lock.wait(cooldownMillis - elapsedTime);
            }

            lastExecutionTime = System.currentTimeMillis();
            // Perform the throttled operation
            System.out.println("Throttled operation executed at: " + lastExecutionTime);
        }
    }

    public void setCooldown(long cooldownMillis) {
        synchronized (lock) {
            this.cooldownMillis = cooldownMillis;
            lock.notify(); // Notify waiting threads that cooldown has changed
        }
    }

    public static void main(String[] args) {
        DynamicThrottling throttling = new DynamicThrottling();

        for (int i = 0; i < 10; i++) {
            try {
                throttling.throttledOperation();
                // Adjust cooldown dynamically
                throttling.setCooldown((i + 1) * 1000); // Cooldown increases each iteration
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


In this example, we introduce the setCooldown method to dynamically adjust the cooldown period. The method uses notify to wake up any waiting threads, allowing them to check the new cooldown period. 

Using Java's Semaphore

Java's Semaphore class can be employed as a powerful tool for throttling. A semaphore maintains a set of permits, where each acquire operation consumes a permit, and each release operation adds one. 

Java
 
public class SemaphoreRateLimiter {

    private final Semaphore semaphore;

    public SemaphoreRateLimiter(int permits) {
        this.semaphore = new Semaphore(permits);
    }

    public boolean throttle() {
        if (semaphore.tryAcquire()) {
            // Perform the throttled operation
            System.out.println("Throttled operation executed. Permits left: " + semaphore.availablePermits());
            return true;
        } else {
            System.out.println("Request throttled. Try again later.");
            return false;
        }
    }

    public static void main(String[] args) {
        SemaphoreRateLimiter rateLimiter = new SemaphoreRateLimiter(5); // Allow 5 operations concurrently

        for (int i = 0; i < 10; i++) {
            rateLimiter.throttle();
        }
    }
}


In this example, the SemaphoreRateLimiter class uses a Semaphore with a specified number of permits. The throttle method attempts to acquire a permit and allows the operation if successful.

Multiple Examples From Box

There are multiple simple solutions provided by frameworks like Spring or Redis.

Spring AOP for Method Throttling

Using Spring's Aspect-Oriented Programming (AOP) capabilities, we can create a method-level throttling mechanism. This approach allows us to intercept method invocations and apply throttling logic.

Java
 
@Aspect
@Component
public class ThrottleAspect {

    private Map<String, Long> lastInvocationMap = new HashMap<>();

    @Pointcut("@annotation(throttle)")
    public void throttledOperation(Throttle throttle) {}

    @Around("throttledOperation(throttle)")
    public Object throttleOperation(ProceedingJoinPoint joinPoint, Throttle throttle) throws Throwable {
        String key = joinPoint.getSignature().toLongString();

        if (!lastInvocationMap.containsKey(key) || System.currentTimeMillis() - lastInvocationMap.get(key) > throttle.value()) {
            lastInvocationMap.put(key, System.currentTimeMillis());
            return joinPoint.proceed();
        } else {
            throw new ThrottleException("Request throttled. Try again later.");
        }
    }
}


In this example, we define a custom @Throttle annotation and an AOP aspect (ThrottleAspect) to intercept methods annotated with @Throttle. The ThrottleAspect checks the time elapsed since the last invocation and allows or blocks the method accordingly. 

Using Guava RateLimiter

Google's Guava library provides a RateLimiter class that simplifies throttling implementation. It allows defining a rate at which operations are permitted.

Let's see how we can use RateLimiter for method throttling:

Java
 
import com.google.common.util.concurrent.RateLimiter;

@Component
public class ThrottledService {

    private final RateLimiter rateLimiter = RateLimiter.create(5.0); // Allow 5 operations per second

    @Throttle
    public void throttledOperation() {
        if (rateLimiter.tryAcquire()) {
            // Perform the throttled operation
            System.out.println("Throttled operation executed.");
        } else {
            throw new ThrottleException("Request throttled. Try again later.");
        }
    }
}


In this example, we use Guava's RateLimiter to control the rate of execution of the throttledOperation method. The tryAcquire method is used to check if an operation is allowed based on the defined rate. 

Redis as a Throttling Mechanism

Using an external data store like Redis, we can implement a distributed throttling mechanism. This approach is particularly useful in a microservices environment where multiple instances need to coordinate throttling.

Java
 
@Component
public class RedisThrottleService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Value("${throttle.key.prefix}")
    private String keyPrefix;

    @Value("${throttle.max.operations}")
    private int maxOperations;

    @Value("${throttle.duration.seconds}")
    private int durationSeconds;

    public void performThrottledOperation(String userId) {
        String key = keyPrefix + userId;
        Long currentCount = redisTemplate.opsForValue().increment(key);

        if (currentCount != null && currentCount > maxOperations) {
            throw new ThrottleException("Request throttled. Try again later.");
        }

        if (currentCount == 1) {
            // Set expiration for the key
            redisTemplate.expire(key, durationSeconds, TimeUnit.SECONDS);
        }

        // Perform the throttled operation
        System.out.println("Throttled operation executed for user: " + userId);
    }
}


In this example, we use Redis to store and manage the count of operations for each user. The performThrottledOperation method increments the count and checks whether the allowed limit has been reached.

Conclusion

Throttling plays a pivotal role in maintaining the stability and scalability of applications. In this article, we explored diverse ways to implement throttling in Java, ranging from simple techniques using Thread.sleep() and Semaphore to apply solutions from the box.

The choice of throttling strategy depends on factors such as the nature of the application, performance requirements, and the desired level of control. When implementing throttling, it's essential to strike a balance between preventing abuse and ensuring a responsive and fair user experience.

As you integrate throttling mechanisms into your applications, consider monitoring and adjusting parameters based on real-world usage patterns. Several inquiries may arise when deciding on a throttling implementation, such as how to handle situations where a task exceeds the allotted period. In the upcoming article, I plan to explore robust Java implementations that address various scenarios comprehensively.

Implementation Java (programming language) Data Types

Opinions expressed by DZone contributors are their own.

Related

  • Generics in Java and Their Implementation
  • Multithreading in Modern Java: Advanced Benefits and Best Practices
  • Apache Spark 3 to Apache Spark 4 Migration: What Breaks, What Improves, What's Mandatory
  • Memory Optimization and Utilization in Java 25 LTS: Practical Best Practices

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook