Oi pessoal,
Estou criando um pool de threads que não permite que elementos considerados iguais estejam ativos ao mesmo tempo.
Na verdade estou utilizando o pool provido pela API padrão do java, e adicionando somente este tratamento de exclusividade.
Aliás se alguém souber que isso já existe pronto na API padrão, por favor não me deixem ficar aqui reinventando a roda.
Mas o que estou fazendo é simples. Por exemplo, minha classe tem o seguinte método:
public void executar(Runnable r){
//primeiro verifica se este Runnable já está ativo no pool
//se não estiver, adiciona ao pool da sun
//se estiver ativo, não faz nada
}
Como esta classe faz para saber se um Runnable já está ativo?
Simples. A classe que implementa Runnable tem que implementar os métodos equals e hashcode tbm.
Assim eu posso utilizar uma coleção de Runnables que eu posso consultar, a qualquer instante, para saber se um determinado Runnable está ativo.
Chegamos então à minha pergunta:
Qual coleção utilizar para manter esta lista de Runnables?
Atualmente estou utilizando um HashSet. Também pensei em ArrayList.
Acho que esta pergunta se resume ao seguinte: Qual das coleções é mais rápida nas operações de add, remove e contains?
pelo que eu sei é o HashSet mesmo, isso é claro dependendo do desempenho do hashCode da sua classe…
e essa lista não permite repetições, se a ideia é não ter dois objetos iguais, você está usando a lista correta…
se um produtor insere as threads na lista e um consumidor starta elas, isso ja é suficiente para não existirem dois iguais executando ao mesmo tempo, detalhe que você ainda deve remover a thread quando ela terminar caso você queira reinseri-la para executar denovo.
para verificar se uma thread está executando, você pode verificar o State do objeto da sua classe (herdado de Thread se eu intendi direito o que você está fazendo), para se for o caso remove-la, você pode deixar uma thread DAEMON fazendo isso de tempo em tempo…
HashSet não permite duplicatas. Porém, a lógica utilizada para adicionar elementos a ele já garante que não haverão elementos duplicados em momento algum. Mesmo que utilize um arraylist, se minha lógica funciona, em momento algum terei elementos repetidos. Foi justamente este ponto que me fez considerar a possibilidade de trocar o HashSet por um ArrayList.
O HashSet garante que não existem elementos duplicados. Logo no seu método add ele primeiro verifica se o elemento já existe. O que não é necessário neste caso. Mais ideias?
então… o HashSet procura usando o HashCode dos objetos na hora de dar um contains, ou de verificar se deve inseri-lo ou não (na verdade nesse caso você nem precisa da o contains, caso você queira essa informação você pode pegar o boolean que o add retorna), sendo assim ele é mais rápido que o ArrayList, este acredito que quando você da um contains( neste caso é necessário), ele itera a lista vendo se o objetoPassado.equals da true com o objeto iterado… o que é uma operação mais lenta…
a unica desvantagem mesmo seria não ter ordenação… o que não me parece fazer diferença… a unica coisa que me parece ajudar a deixar mais rápido ai seria você tirar o contains no caso do HashSet, ja que o add básicamente faz isso antes de adicionar.
É isso mesmo. Cheguei a esta conclusão também.
O que era:
if (! isAtivo(runnable)){//dá um contains na coleção pra ver se o elemento já existe
setAtivo (runnable,true); //adiciona ou remove à coleção dependendo do segundo parâmetro
pool.execute(new InternalRunnable (runnable));
}
if (setAtivo (runnable,true)){//adiciona somente se o elemento não existir. Economizei um contains aqui.. hehehe
pool.execute(new InternalRunnable (runnable));
}
De fato, esta deve ser a melhor estratégia. A menos que já exista um pool de threads com exclusividade e eu esteja reinventando a roda. Não encontrei nada na api do java.
O método contains do hashSet também tem tempo de o(1). Do ArrayList vai ter algo próximo de O(n/2)
Aliás, aqui está a minha classe completa, caso alguém tenha interesse:
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Gerenciador de threads. Garante que, em determinado instante, somente haverá uma thread executando sobre um determinado
* objeto. Os objetos passados para o método executar, além de implementar Runnable, devem implementar equals e hashcode para
* que a classe consiga determinar quais objetos estão ativos em determinado instante.
* @author Rodrigo Bossini
* @since 02/02/2010
* */
public class ExclusiveThreadManager {
private ExecutorService pool;
private Set <Runnable> ativos = new HashSet<Runnable>();
/**
* Constrói um pool de threads que cresce de acordo com a necessidade. Threads que já terminaram seu serviço permanecem
* ativas durante um minuto. Se outra atividade aparecer, uma thread ociosa pode ser reaproveitada. Após um minuto
* de ociosidade, a thread é eliminada.
* @author Rodrigo Bossini
* @since 02/02/2010
* */
public ExclusiveThreadManager(){
pool = Executors.newCachedThreadPool();
}
/**
* Costrói um pool de threads com um número de threads igual a numThreads.
* @author Rodrigo Bossini
* @since 02/02/2010
* */
public ExclusiveThreadManager (int numThreads){
pool = Executors.newFixedThreadPool(numThreads);
}
/**
* Verifica (com base nos métodos equals e hashcode) se este objeto runnable está sendo executado atualmente.
* Em caso positivo, simplesmente retorna. Em caso contrário, adiciona o mesmo para a fila de execução.
* */
public void executar (Runnable runnable){
if (setAtivo (runnable,true)){
//o runnable a executar é empacotado em um Runnable Interno. O método run deste Runnable interno chamada o método run deste runnable e então exclui o objeto da lista de ativos.
pool.execute(new InternalRunnable (runnable));
}
}
private boolean setAtivo (Runnable runnable, boolean ativar){
if (ativar){
synchronized (ativos){
return ativos.add(runnable);
}
}
else
{
synchronized (ativos){
return ativos.remove(runnable);
}
}
}
private class InternalRunnable implements Runnable{
private Runnable internalRunnable;
public void run() {
try{
internalRunnable.run();
}
finally{
setAtivo (internalRunnable, false);
}
}
public InternalRunnable (Runnable runnable){
this.internalRunnable = runnable;
}
}
/**
* Espera que todas as atividades que já tenham tido o pedido de execução realizado terminem para finalizar o pool de threads.
* Depois desta chamada, outras atividades serão rejeitadas.
* @author Rodrigo Bossini
* @since 02/02/2010
* */
public void fechar (){
pool.shutdown();
}
/**
* Fecha o pool imediatamente sem se preocupar se ainda existem atividades sendo executadas.
* @author Rodrigo Bossini
* @since 02/02/2010
* */
public void fecharAgora (){
pool.shutdownNow();
}
}