Coordenacao de delay entre threads

Depois de descobrir como saber se uma thread conseguiu ou nao um lock (http://www.guj.com.br/posts/list/19700.java), agora estou com um problema mais interessante e desafiador. :smiley:

Imagine um processo multi-thread que faz requisicoes a um servidor HTTP. Esse processo eh relativamente rapido e nao queremos sobrecarregar o servidor. Dessa forma pensei no seguinte: estabelecer um delay entre uma e outra requisicao. Se isso fosse serial (uma thread), resolveria, mas em paralelo precisa de uma coordenacao entre as threads.

A implementacao ficou assim (o Runner eh o mesmo do topico acima):

ProcessoDelay:

[code]import EDU.oswego.cs.dl.util.concurrent.Mutex;

public class ProcessoDelay {

private static final int DELAY_REQUEST = 1000; // em milissegundos
private static final int SLEEP_TIME = DELAY_REQUEST / 2;

private static final Mutex mutex = new Mutex();
private static long lastTime = System.currentTimeMillis() - DELAY_REQUEST;

private String nome;

public void initTask(String nome) {
    this.nome = nome;
    try {
        acquireLockTime();
        doTask();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        releaseLockTime();
    }
}

private void doTask() throws Exception {
    System.out.println("thread " + nome + " - time: " + System.currentTimeMillis() + " - delay:" + (System.currentTimeMillis() - lastTime));
    try {
        Thread.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

private void acquireLockTime() throws InterruptedException {
    while (true) {
        // aqui acho que poderia ser simplesmente: while (!mutex.attempt(SLEEP_TIME));
        while (!mutex.attempt(0)) {
            Thread.sleep(SLEEP_TIME);
        }
        if (System.currentTimeMillis() <= (lastTime + DELAY_REQUEST)) {
            mutex.release();
        } else {
            break;
        }
    }
}

private void releaseLockTime() {
    lastTime = System.currentTimeMillis();
    mutex.release();
}

}[/code]

Resultado:

thread 2 - time: 1107526591984 - delay:1010 thread 16 - time: 1107526592996 - delay:1002 thread 19 - time: 1107526594007 - delay:1001 thread 20 - time: 1107526595038 - delay:1001 thread 3 - time: 1107526596050 - delay:1002 thread 1 - time: 1107526597061 - delay:1001 thread 4 - time: 1107526598073 - delay:1002 thread 6 - time: 1107526599084 - delay:1001 thread 12 - time: 1107526600096 - delay:1002 thread 7 - time: 1107526601107 - delay:1001 thread 18 - time: 1107526602119 - delay:1002 thread 13 - time: 1107526603130 - delay:1001 thread 5 - time: 1107526604142 - delay:1002 thread 9 - time: 1107526605153 - delay:1001 thread 14 - time: 1107526606184 - delay:1001 thread 15 - time: 1107526607196 - delay:1002 thread 8 - time: 1107526608207 - delay:1001 thread 17 - time: 1107526609219 - delay:1002 thread 11 - time: 1107526610230 - delay:1001 thread 10 - time: 1107526611242 - delay:1002

Existe uma maneira melhor/mais simples de resolver esse problema? Opinioes sao bem-vindas. :smiley:

Marcio Kuchma

Você quer algo como um lock com efeito retardado? Quem estiver na espera só vai ganhar o lock depois de X segundos que ela for liberada?

Te tal algo assim (adaptado da classe mutex do dl):

  public boolean acquire() throws InterruptedException {
    if (Thread.interrupted()) throw new InterruptedException();
    synchronized(this) {
    	long current = System.currentTimeMillis();
    	long delta = (current - start);
      if (!inuse && delta >= DELAY) {
        inuse = true;
        return;
      }
      else {
        try {
        	 long waitTime = DELAY - delta;
          for (;;) {
            wait(waitTime);
	    	   current = System.currentTimeMillis();
	    	   delta = (current - start);
            if (!inuse && delta >= DELAY) {
              inuse = true;
              return;
            }
            else {
              waitTime = DELAY - delta;
            }
          }
        }
        catch (InterruptedException ex) {
          notify();
          throw ex;
        }
      }
    }
  }

[quote=louds]Você quer algo como um lock com efeito retardado? Quem estiver na espera só vai ganhar o lock depois de X segundos que ela for liberada?

Te tal algo assim (adaptado da classe mutex do dl):[/quote]

Otima ideia. Dessa forma a complexidade do gerenciamento de tempo e tal fica tudo na classe do monitor, sendo abstraida do processo que apenas faz uso do monitor.

Porem minha necessidade eh um pouco diferente - um lock com efeito retardado, mas considerando o tempo que o lock anterior gastou. Exemplo: se o delay for de 500 e o processo gastou 100, o proximo lock soh sera liberado apos um wait de 400. Ja se o processo gastou 600, o proximo lock pode ser liberado imediatamente.

Entao ficou assim:

ProcessoDelayMutex (ficou cuti-cuti - como deveria ser):

[code]public class ProcessoDelayMutex {

private static final int DELAY_REQUEST = 500; // em milissegundos
private static final MutexDelay mutex = new MutexDelay(DELAY_REQUEST);

public void initTask() {
    try {
        mutex.acquire();
        doTask();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        mutex.release();
    }
}

private void doTask() throws Exception {
    try {
        Thread.sleep((long)(Math.random() * 1000));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

}[/code]

MutexDelay (adaptacao da tua ideia):

[code]/**

  • Adaptação do Mutex de Doug Lea para que haja um intervalo mínimo entre a

  • obtenção dos locks.
    */
    public class MutexDelay {

    /** Quantidade de milissegundos estabelecida como intervalo mínimo entre um lock e outro. */
    private long delay;

    /** Status do lock. */
    private boolean locked = false;

    /** Momento em que o último lock foi obtido. */
    private long lockTime = 0;

    /** Limite mínimo para a liberação do próximo lock. */
    private long nextLockTime = 0;

    /**

    • Construtor da classe. Recebe como parâmetro a quantidade de milissegundos
    • que deve ser utilizada como intervalo mínimo entre a liberação dos locks.
    • @param delay
      */
      public MutexDelay(long delay) {
      this.delay = delay;
      }

    /**

    • Obtém o lock do monitor.

    • @throws InterruptedException
      */
      public void acquire() throws InterruptedException {
      if (Thread.interrupted()) {
      throw new InterruptedException();
      }

      long currentTime = 0;
      long waitTime = 0;
      long defaultWaitTime = this.delay / 2;

      synchronized (this) {
      currentTime = System.currentTimeMillis();
      waitTime = this.nextLockTime - currentTime;
      if ((this.locked) || (waitTime > 0)) {
      try {
      do {
      wait((waitTime > 0) ? waitTime : defaultWaitTime);
      currentTime = System.currentTimeMillis();
      waitTime = this.nextLockTime - currentTime;
      } while ((this.locked) || (waitTime > 0));
      } catch (InterruptedException e) {
      notify();
      throw e;
      }
      }
      this.locked = true;
      this.lockTime = currentTime;
      }
      }

    /**

    • Libera o lock do monitor.
      */
      public synchronized void release() {
      long currentTime = System.currentTimeMillis();
      long delta = currentTime - this.lockTime;
      this.nextLockTime = currentTime;
      if (this.delay > delta) {
      this.nextLockTime += (this.delay - delta);
      }

      // TODO remover
      System.err.println("acquire: " + this.lockTime
      + " - release: " + currentTime
      + " - delta: " + delta
      + " - nextLockTime: " + this.nextLockTime
      + " - next wait: " + (this.nextLockTime - currentTime));

      this.locked = false;
      notify();
      }

}[/code]

Fiz alguns testes e funcionou a contento. Caso queira, deixe sua opiniao. :smiley:

Marcio Kuchma