Spring controller how to serve only 1 request each time, and discard other requests received for the same method until first request has finished



So as the title describes I want to achieve the following

@Controller
public class ImportController {


    @RequestMapping(value = "/{File}", method = RequestMethod.GET)
    @LogAware
    public String import(@PathVariable(value = "File") String excel, Model model) {

        try {
            synchronized (this) {

            //code...

          }
       }

   }
}

I want the code to be executed only for 1 request that comes at a time. The execution of the code inside the synchronized block can last about 1 hour. In the mean time I would like each other request that arrives to that method to be cancelled. Is there any way to achieve that?

Just to clarify:

As it is right now the first request will be served and when it finishes the next request that was waiting for the lock will be served and then the next that was waiting.

What I want is to not allow other requests which are already waiting to be served after the first request finishes. If the requests came during the execution of the first request I want to return bad request or something else to the user and to cancel their request.

Answer

Approach 1:

Use a single permit Semaphore

Here’s a sample code:

import java.util.concurrent.Semaphore;

public class Test {
    Semaphore s = new Semaphore(1); // Single permit.

    public void nonBlockingMethod() throws InterruptedException {
        // A thread tries to acquire a permit, returns immediately if cannot
        if (s.tryAcquire()) {
            // No. of permits = 0
            try {
                System.out.println(Thread.currentThread().getName() + " begins execution..");

                // long running task
                Thread.sleep(4000);

                System.out.println(Thread.currentThread().getName() + " exiting..");
            } finally {
                s.release(); // Release permit. No. of permits = 1
            }
        } else {
            System.out.println(Thread.currentThread().getName() + " cannot run as another thread is already running..");
        }
    }
}

Approach 2:

Use a ReentrantLock

Sample Code:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {
    Lock s = new ReentrantLock();

    public void nonBlockingMethod() throws InterruptedException {
        if (s.tryLock()) {
            try {
                System.out.println(Thread.currentThread().getName() + " begins execution..");

                // long running task
                Thread.sleep(4000);

                System.out.println(Thread.currentThread().getName() + " exiting..");
            } finally {
                s.unlock();
            }
        } else {
            System.out.println(Thread.currentThread().getName() + " cannot run as another thread is already running..");
        }
    }
}

Driver:

public static void main(String[] args) throws InterruptedException {
    Test t = new Test();

    Runnable r = () -> {
        try {
            t.nonBlockingMethod();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };

    for (int i = 0; i < 3; i++) {
        new Thread(r, "Loop-1-Thread-" + i).start();
    }

    Thread.sleep(3999);

    // one of the threads in this iteration may get to run the task
    for (int i = 3; i < 8; i++) {
        new Thread(r, "Loop-2-Thread-" + i).start();
    }
}

(one of the) Output (s):

Loop-1-Thread-2 cannot run as another thread is already running..
Loop-1-Thread-1 cannot run as another thread is already running..
Loop-1-Thread-0 begins execution..
Loop-2-Thread-3 cannot run as another thread is already running..
Loop-2-Thread-4 cannot run as another thread is already running..
Loop-2-Thread-5 cannot run as another thread is already running..
Loop-1-Thread-0 exiting..
Loop-2-Thread-6 begins execution..
Loop-2-Thread-7 cannot run as another thread is already running..
Loop-2-Thread-6 exiting..


Source: stackoverflow