membersound
membersound

Reputation: 86757

How to throttle method invocation in Spring?

class PublishService {
    public void longRunningPublish() {
        ...
    }
}

From different places in code, the method can be invoked.

caller1.longRunningPublish();
caller2.longRunningPublish();
...
callerN.longRunningPublish();

Question: how can I prevent longRunningPublish running concurrently? Each invocation should stack and be delay, and only start when the previous run has finished.

Could I do better than the following?

class PublishService {
    private boolean running;

        public void throttleLongRunningPublish() {
            while (running) {
                TimeUnit.SECONDS.sleep(10);
            }   
            
            running = true;
            
            try {
               longRunningPublish();
            } finally {
               running = false;
            }
        }
}

Upvotes: 0

Views: 409

Answers (3)

membersound
membersound

Reputation: 86757

I found a neat way with Semaphore:

class PublishService {
    private static final Semaphore lock = new Semaphore(1);

    public void throttleLongRunningPublish() {
        try {
            lock.tryAcquire(2, TimeUnit.MINUTES);
        
            longRunningPublish();
        } finally {
            lock.release();
        }
    }
}        

Upvotes: 0

Giovanni
Giovanni

Reputation: 4015

Your code is not thread safe. If you create multiple instances of PublishService and run them concurrently the boolean variable has no effect. If your instance of PublishService is a singleton and the same class is executed by different threads there there is no guarantee that the method will be executed serially because multiple thread could enter the method before reaching the instruction:

running = true;

This is a simple example than handles serialization if there are multiple instances of the same class along with a "demo" main

public class PublishService {
private static final Logger logger= LoggerFactory.getLogger(PublishService.class.getName());

private static final Lock lock=new ReentrantLock();
public void longRunningPublish() {
    lock.lock();
    try {
        logger.info("{} longRunningPublish before sleep",Thread.currentThread().getId());
        Thread.sleep(500);
        logger.info("{} longRunningPublish after sleep",Thread.currentThread().getId());
    } catch (InterruptedException e) {
       logger.error(e.getMessage(),e);
    } finally {
        lock.unlock();
    }

}

public static void main(String args[]) {
    ExecutorService executor=Executors.newFixedThreadPool(10);
    for(int i=0;i<20;i++) {
        executor.submit(() -> {
            PublishService publishService = new PublishService();
            publishService.longRunningPublish();
        });
    }
}

}

If the class is a singleton you can remove the static qualifier of the lock variable.

Upvotes: 1

GS3
GS3

Reputation: 21

In order to prevent concurrent access, you need to lock the resource while it is being used with something like a ReentrantLock. If you need to guarantee in-order access, you can use the constructor ReentrantLock(boolean fair) with fair set to true. Otherwise, you can use a basic ReentractLock or the synchronized property.

Upvotes: 0

Related Questions