Coordenacao de delay entre threads

2 respostas
kuchma

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. :D

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:
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();
    }

}
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. :D

Marcio Kuchma

2 Respostas

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):

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;
        }
      }
    }
  }
kuchma
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):

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):

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();
        }
    }

}

MutexDelay (adaptacao da tua ideia):

/**
 * 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();
    }

}

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

Marcio Kuchma

Criado 4 de fevereiro de 2005
Ultima resposta 10 de fev. de 2005
Respostas 2
Participantes 2