2

I'm looking for a way to limit access to a method in java to no more than once every X seconds. Here's my situation :

I want to run this code in parallel with multiple threads :

private MyService service;
public void run() {
    // Send request to remote service
    InputStream response = service.executeRequest();

    // Process response
    ... some more code
}

The executeRequest() method sends an http request to a remote server (which isn't mine, I have no access to its implementation) and waits for the response from the server. It then does some processing of the data. I would like to have many threads run this in parallel. My problem is that the remote server will crash if too many requests are sent simultaneously. So I want some way of making sure that the executeRequest() method will never be called more than once every second.

Do you know how I could do that in java ? Thanks

jonasr
  • 1,876
  • 16
  • 18
  • 1
    What about limiting the number of concurrent executions and blocking new calls until a service instance is available? Are you using any particular technology for your remote server? – Thomas Sep 30 '11 at 14:35
  • The remote server isn't mine. MyService.executeRequest sends an http request to the remote server and waits for the response. I have no control over it. – jonasr Sep 30 '11 at 15:01

6 Answers6

4

Hrm, I'm not sure that restricting the frequency of access to a method will result in prevention of overload.

Perhaps there isn't enough info in the above post, but it seems that a WorkerThread + JobQueue setup would work just fine here.

Food for thought: Multithreaded job queue manager

EDIT: Trying to be a bit less vague...

  • Have the server collect the requests into some data structure, perhaps a class called Job.
  • Have Jobs then be placed into the bottom of a Queue.
  • Have WorkerThread objects pop Job objects off the top of the Queue and process them.
  • Make sure to only instantiate as many WorkerThread objects as you need to maintain proper server load. Only experimentation will determine this number, but as a very rough rule, start with # of processing cores - 1. (aka start 7 workers on an 8 core machine)

EDIT #2 In light of new information:

  • Setup a Queue on the client side
  • Make a Worker that can track what Jobs have been submitted, which Jobs have gotten a response and which Jobs are still processing. This will allow for limiting the number of Jobs being submitted at any one time.
  • Make Worker track 'lastSubmissionTime' to prevent any submission occuring < 1 second from previous
Community
  • 1
  • 1
claymore1977
  • 1,385
  • 7
  • 12
  • Sorry, i wasn't precise enough. The remote server isn't mine. I'm sending http requests to the remote server and waiting for the response. I don't know how it's built, but sending too many requests in too short intervals will make it crash, which is why i want to limit the number of requests i'm sending to one every second – jonasr Sep 30 '11 at 15:03
  • Ah, in that case, build the Job and JobQueue on the client side and have a single Worker manage the Job submissions to the server. You could set it up so that the Worker submits one job per second, or you could have the Worker keep track of how many outstanding jobs there are by tracking submissions AND responses... or do both :) – claymore1977 Sep 30 '11 at 15:09
2

You could use a Semaphore to throttle the number of threads able to call executeRequest():

http://download.oracle.com/javase/1,5,0/docs/api/java/util/concurrent/Semaphore.html

The executing thread could increment the semaphore prior to entering the execute and other threads could wait for it to fall to either 0 or a number which reflects how many are allowed to run in parallel.

A Timertask:

http://download.oracle.com/javase/1.4.2/docs/api/java/util/TimerTask.html

Could be used to decrement the semaphore after 3 seconds... Throttling the entry to no more than 1 new entrant every 3 seconds:

Benj
  • 31,668
  • 17
  • 78
  • 127
1

Limiting concurrency on the client side is not a good pattern — how are clients supposed to know about each other?

MaDa
  • 10,511
  • 9
  • 46
  • 84
0

Have you thought about using sleep to have the threads pause before jumping to hit the remote call? You could have the threads sleep for a random # of seconds between 1 and 5 which would limit the number of threads hitting the method at any one time.

You could also put a lock on the method that expires after 1 second so each thread "grabs" the lock, executes the method, but its lock expires so the next thread can grab it and execute. Put the lock at the beginning of the method -- and have it do nothing except hold the thread for one second thread.sleep(1000) and then continue execution. That will limit you to one thread hitting the method at a time.

EDIT: Response to OP's Comment Below

   class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() { 
     lock.lock();  // block until condition holds
     try {
       thread.sleep(1000) //added to example by floppydisk.
     } finally {
       lock.unlock()
       doYourConnectionHere();
     }
   }
 }

Modified from: ReentrantLock. Inside the try/catch, all you do is thread.sleep(1000) instead of actually doing something. It then releases the lock for the next thread and continues executing the rest of the method body--in your case the connection to the remote server.

FloppyDisk
  • 1,693
  • 16
  • 25
  • Your first suggestion doesn't really work for me, because there is nothing preventing two threads from awakening at the same time and calling the service almost simultaneously. Your second suggestion is kind of what i'm looking for but I haven't found an example of it. – jonasr Sep 30 '11 at 15:15
  • @jonasr: Updated the answer to provide a better code example. – FloppyDisk Sep 30 '11 at 15:20
  • The problem with this is that every thread will wait 1 second before calling the method even if they don't have to. It's maybe not much but if I need to increase the delay it could be a problem – jonasr Sep 30 '11 at 16:25
  • Part of the problem with threading is you cannot guarantee order of execution. If you start two threads, thread1 and thread2 you cannot assume that thread1 will access the method before thread2 takes a shot at it based on how the processor's scheduler works. If you need to increase delay, use the random time wait idea. that would space out execution even more. – FloppyDisk Sep 30 '11 at 16:57
0

Using dynamic proxy you can wrap your service and handle max execution in the InvocationHandler:

MyService proxy = (MyService) Proxy.newProxyInstance( //
    MyService.class.getClassLoader(), //
    new Class[] {MyService.class}, //
    new MaxInvocationHandler());

where naive implementation of InvocationHandler could look something like this:

  class MaxInvocationHandler implements InvocationHandler {
    private static final long MAX_INTERVAL = 1000L;
    private static final long MAX_INVOCATIONS = 1;

    AtomicLong time = new AtomicLong();
    AtomicLong counter = new AtomicLong();

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      long currentTime = System.currentTimeMillis();
      if (time.get() < currentTime) {
        time.set(currentTime + MAX_INTERVAL);
        counter.set(1);
      } else if(counter.incrementAndGet() > MAX_INVOCATIONS) {
        throw new RuntimeException("Max invocation exceeded");
      }

      return method.invoke(proxy, args);
    }
  }
Eugene Kuleshov
  • 31,461
  • 5
  • 66
  • 67
0

In your controller class where you call worker to execute the service, use ExecutorService to start the pool of threads to ultilize the worker class.

ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new MyServiceImpl(someObject));

To add to what others have already suggested, Have a worker class to take task from the queue execute and wait for the number of minutes as you require before taking another task from the queue. I have made this 2min as an example.

Example:

public class MyServiceImpl implements MyService , Runnable {

  public static final int MAX_SIZE = 10;
  private final BlockingQueue<Object> queue = new ArrayBlockingQueue<Object>(MAX_SIZE);

    @Override
    public void run() {
    try
    {
     Object obj;
      while ((obj==queue.take()) != null)
      {
       executeRequest(obj);
       //wait for 2 min
       Thread.sleep(1000 * 60 * 2);
      }
    }
    catch (InterruptedException e)
    {}
  }

  public void executeRequest(Object obj)
  {
    // do routine
  }  

  public MyServiceImpl (Object token)
  {
    try
    {
      queue.put(token);
    }
    catch (InterruptedException e)
    {
      throw new AssertionError(e);
    }
  }
}
Bitmap
  • 12,402
  • 16
  • 64
  • 91