0

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.

Panagiotis Bougioukos
  • 15,955
  • 2
  • 30
  • 47
  • 5
    You can try to replace synchronized block with Reentrant lock and use tryLock() method in that class. If any other thread has held the lock then this method will return false and you can complete the request. – Anmol Bhatia May 28 '21 at 13:53
  • *What I want is to not allow other requests to be served after the first request finishes. * Do you mean that the method in question is expected to execute only once the app's lifecycle? – adarsh May 28 '21 at 14:31
  • @Adarsh What I want is to not allow other requests which are already waiting to be served after the first request finishes – Panagiotis Bougioukos May 28 '21 at 17:30
  • In a sense whatever comes during the execution of some other request must be cancelled. – Panagiotis Bougioukos May 28 '21 at 17:42

2 Answers2

1

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..
adarsh
  • 1,393
  • 1
  • 8
  • 16
0

This is an approach that you can consider. This uses a global state in an AtomicBoolean which is safe (?) to use in your use case, hopefully!

See this SO When do I need to use AtomicBoolean in Java?

static AtomicBoolean atomicBoolean  = new AtomicBoolean(false);

    //controller definition
        if(atomicBoolean.compareAndSet(false, true)) {
            // your logic
            atomicBoolean.compareAndSet(true, false);
        }

    // rest of the controller logic

But, do consider an option of queueing the requests and processing them as a background task or so. Keeping the socket and HTTP open for longer times is not recommended in most cases.

aksappy
  • 3,400
  • 3
  • 23
  • 49
  • That's not thread-safe, you're not using the AtomicBoolean correctly. the question that you link to does have the correct usage of AtomicBoolean in its answers. – Erwin Bolwidt May 28 '21 at 14:03