Como saber se outros tópicos terminaram?

126

Eu tenho um objeto com um método chamado StartDownload(), que inicia três threads.

Como recebo uma notificação quando cada thread termina a execução?

Existe uma maneira de saber se um (ou todos) do thread está concluído ou ainda está em execução?

Ricardo Felgueiras
fonte
1
Dê uma olhada na classe
Fortyrunner 31/03/09

Respostas:

228

Existem várias maneiras de fazer isso:

  1. Use Thread.join () no thread principal para aguardar de forma bloqueadora a conclusão de cada Thread, ou
  2. Verifique Thread.isAlive () de uma forma de pesquisa - geralmente desencorajada - para esperar até que cada Thread seja concluído ou
  3. Não ortodoxo, para cada Thread em questão, chame setUncaughtExceptionHandler para chamar um método em seu objeto e programe cada Thread para lançar uma Exceção não capturada quando concluir, ou
  4. Use bloqueios ou sincronizadores ou mecanismos de java.util.concurrent ou
  5. Mais ortodoxo, crie um ouvinte no seu Thread principal e programe cada um dos Threads para informar ao ouvinte que eles foram concluídos.

Como implementar a Idéia # 5? Bem, uma maneira é primeiro criar uma interface:

public interface ThreadCompleteListener {
    void notifyOfThreadComplete(final Thread thread);
}

em seguida, crie a seguinte classe:

public abstract class NotifyingThread extends Thread {
  private final Set<ThreadCompleteListener> listeners
                   = new CopyOnWriteArraySet<ThreadCompleteListener>();
  public final void addListener(final ThreadCompleteListener listener) {
    listeners.add(listener);
  }
  public final void removeListener(final ThreadCompleteListener listener) {
    listeners.remove(listener);
  }
  private final void notifyListeners() {
    for (ThreadCompleteListener listener : listeners) {
      listener.notifyOfThreadComplete(this);
    }
  }
  @Override
  public final void run() {
    try {
      doRun();
    } finally {
      notifyListeners();
    }
  }
  public abstract void doRun();
}

e cada um dos seus Threads será estendido NotifyingThreade, em vez de implementá- run()lo, será implementado doRun(). Assim, quando concluírem, notificarão automaticamente qualquer pessoa que aguarde a notificação.

Finalmente, em sua classe principal - aquela que inicia todos os Threads (ou pelo menos o objeto que está aguardando notificação) - modifique essa classe para implement ThreadCompleteListenere imediatamente após a criação de cada Thread, adicione-se à lista de ouvintes:

NotifyingThread thread1 = new OneOfYourThreads();
thread1.addListener(this); // add ourselves as a listener
thread1.start();           // Start the Thread

então, à medida que cada thread sai, seu notifyOfThreadCompletemétodo será chamado com a instância Thread que acabou de concluir (ou travar).

Note-se que melhor seria implements Runnable, em vez de extends Threadpara NotifyingThreadcomo a extensão da linha é geralmente desencorajado em novo código. Mas estou codificando para sua pergunta. Se você alterar a NotifyingThreadclasse a ser implementada Runnable, precisará alterar parte do seu código que gerencia os Threads, o que é bastante simples de fazer.

Eddie
fonte
Oi!! Eu gosto da última ideia. Eu para implementar um ouvinte para fazer isso? Obrigado
Ricardo Felgueiras
4
mas, usando essa abordagem, o notifiyListeners é chamado dentro de run (), por isso será chamado dentro do thread e as chamadas adicionais também serão feitas lá, não é assim?
Jordi Puigdellívol 15/03
1
@ Eddie Jordi estava perguntando, se é possível chamar o notifymétodo não dentro do runmétodo, mas depois dele.
Tomasz Dzięcielewski
1
A questão realmente é: como você começa OFF do thread secundário agora. Sei que está concluído, mas como faço para acessar o thread principal agora?
Patrick
1
Este tópico é seguro? Parece que notifyListeners (e, portanto, notifyOfThreadComplete) serão chamados dentro do NotifyingThread, em vez de dentro do segmento que criou, criou o próprio Ouvinte.
Aaron
13

Solução usando CyclicBarrier

public class Downloader {
  private CyclicBarrier barrier;
  private final static int NUMBER_OF_DOWNLOADING_THREADS;

  private DownloadingThread extends Thread {
    private final String url;
    public DownloadingThread(String url) {
      super();
      this.url = url;
    }
    @Override
    public void run() {
      barrier.await(); // label1
      download(url);
      barrier.await(); // label2
    }
  }
  public void startDownload() {
    // plus one for the main thread of execution
    barrier = new CyclicBarrier(NUMBER_OF_DOWNLOADING_THREADS + 1); // label0
    for (int i = 0; i < NUMBER_OF_DOWNLOADING_THREADS; i++) {
      new DownloadingThread("http://www.flickr.com/someUser/pic" + i + ".jpg").start();
    }
    barrier.await(); // label3
    displayMessage("Please wait...");
    barrier.await(); // label4
    displayMessage("Finished");
  }
}

label0 - barreira cíclica é criada com um número de partes igual ao número de encadeamentos em execução mais um para o encadeamento principal de execução (no qual startDownload () está sendo executado)

label 1 - n-th DownloadingThread entra na sala de espera

a etiqueta 3 - NUMBER_OF_DOWNLOADING_THREADS entrou na sala de espera. O encadeamento principal de execução os libera para começar a fazer os trabalhos de download mais ou menos ao mesmo tempo

etiqueta 4 - o fio principal de execução entra na sala de espera. Esta é a parte 'mais complicada' do código para entender. Não importa qual thread entrará na sala de espera pela segunda vez. É importante que qualquer segmento que entre na sala por último garanta que todos os outros segmentos de download concluam seus trabalhos de download.

A etiqueta 2 - n-ésimo DownloadingThread terminou o trabalho de download e entra na sala de espera. Se for o último, ou seja, NUMBER_OF_DOWNLOADING_THREADS já o inseriu, incluindo o thread principal de execução, o thread principal continuará sua execução somente quando todos os outros threads tiverem terminado o download.

Boris Pavlović
fonte
9

Você realmente deve preferir uma solução que usejava.util.concurrent . Encontre e leia Josh Bloch e / ou Brian Goetz sobre o assunto.

Se você não estiver usando java.util.concurrent.*e estiver assumindo a responsabilidade de usar os Threads diretamente, provavelmente deverá join()saber quando um thread será concluído. Aqui está um mecanismo de retorno de chamada super simples. Primeiro estenda a Runnableinterface para ter um retorno de chamada:

public interface CallbackRunnable extends Runnable {
    public void callback();
}

Em seguida, crie um Executor que executará o seu executável e retornará a ligação quando terminar.

public class CallbackExecutor implements Executor {

    @Override
    public void execute(final Runnable r) {
        final Thread runner = new Thread(r);
        runner.start();
        if ( r instanceof CallbackRunnable ) {
            // create a thread to perform the callback
            Thread callerbacker = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // block until the running thread is done
                        runner.join();
                        ((CallbackRunnable)r).callback();
                    }
                    catch ( InterruptedException e ) {
                        // someone doesn't want us running. ok, maybe we give up.
                    }
                }
            });
            callerbacker.start();
        }
    }

}

O outro tipo de coisa óbvia a ser adicionada à sua CallbackRunnableinterface é um meio de lidar com quaisquer exceções; portanto, talvez coloque uma public void uncaughtException(Throwable e);linha lá e no seu executor, instale um Thread.UncaughtExceptionHandler para enviar você para esse método de interface.

Mas fazer tudo isso realmente começa a cheirar java.util.concurrent.Callable. Você realmente deve usar java.util.concurrentse o seu projeto permitir.

broc.seib
fonte
Eu sou um pouco incerto sobre o que você ganha com esse mecanismo de retorno de chamada, simplesmente chamando runner.join()e, em seguida, o código que desejar depois disso, já que você sabe que o segmento terminou. É apenas que você define esse código como uma propriedade do executável, para poder ter coisas diferentes para diferentes executáveis?
Stephen
2
Sim, runner.join()é a maneira mais direta de esperar. Eu estava assumindo que o OP não queria bloquear seu segmento de chamada principal, pois eles pediram para serem "notificados" para cada download, o que poderia ser concluído em qualquer ordem. Isso ofereceu uma maneira de ser notificado de forma assíncrona.
Broc.seib
4

Deseja esperar que eles terminem? Nesse caso, use o método Join.

Há também a propriedade isAlive se você quiser apenas verificá-la.

Jonathan Allen
fonte
3
Observe que isAlive retorna false se o segmento ainda não começou a executar (mesmo se seu próprio segmento já tiver chamado start nele).
Tom Hawtin - tackline
@ TomHawtin-tackline você tem certeza disso? Isso contradiz a documentação do Java ("Um encadeamento está ativo se foi iniciado e ainda não morreu" - docs.oracle.com/javase/6/docs/api/java/lang/… ). Ele também iria contradizer as respostas aqui ( stackoverflow.com/questions/17293304/... )
Stephen
@ Stephen Há muito tempo desde que escrevi isso, mas parece ser verdade. Imagino que tenha causado problemas a outras pessoas que estavam frescas em minha memória há nove anos. Exatamente o que é observável dependerá da implementação. Você diz Threada start, o que o encadeamento faz, mas a chamada retorna imediatamente. isAlivedeveria ser um teste simples de flag, mas quando pesquisei no Google o método era native.
Tom Hawtin - defina
4

Você pode interrogar a instância do encadeamento com getState (), que retorna uma instância da enumeração Thread.State com um dos seguintes valores:

*  NEW
  A thread that has not yet started is in this state.
* RUNNABLE
  A thread executing in the Java virtual machine is in this state.
* BLOCKED
  A thread that is blocked waiting for a monitor lock is in this state.
* WAITING
  A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
* TIMED_WAITING
  A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
* TERMINATED
  A thread that has exited is in this state.

No entanto, acho que seria um design melhor ter um encadeamento mestre que aguarda a conclusão dos 3 filhos; o mestre continuará a execução quando os outros 3 terminarem.

Miquel
fonte
Esperar a saída dos três filhos pode não se encaixar no paradigma de uso. Se esse é um gerenciador de downloads, eles podem iniciar 15 downloads e simplesmente remover o status da barra de status ou alertar o usuário quando o download for concluído, caso em que um retorno de chamada funcionaria melhor.
digitaljoel
3

Você também pode usar o Executorsobjeto para criar um pool de threads ExecutorService . Em seguida, use o invokeAllmétodo para executar cada um de seus threads e recuperar futuros. Isso bloqueará até que todos concluam a execução. Sua outra opção seria executar cada um usando o pool e depois chamar awaitTerminationpara bloquear até que o pool termine de executar. Lembre-se de ligar para shutdown() quando terminar de adicionar tarefas.

J Gitter
fonte
2

Muitas coisas foram alteradas nos últimos 6 anos na frente multi-threading.

Ao invés de usar join() e bloquear a API, você pode usar

1. API ExecutorService invokeAll()

Executa as tarefas fornecidas, retornando uma lista de futuros mantendo seu status e resultados quando todos estiverem concluídos.

2. CountDownLatch

Um auxiliar de sincronização que permite que um ou mais encadeamentos esperem até que um conjunto de operações sendo executadas em outros encadeamentos seja concluído.

A CountDownLatché inicializado com uma determinada contagem. Os métodos de espera são bloqueados até que a contagem atual atinja zero devido a invocações do countDown()método, após o qual todos os encadeamentos em espera são liberados e quaisquer invocações subseqüentes de aguardam retornam imediatamente. Este é um fenômeno de uma vez - a contagem não pode ser redefinida. Se você precisar de uma versão que redefina a contagem, considere usar um CyclicBarrier.

3. ForkJoinPool ou newWorkStealingPool()em executores é outra maneira

4.Iterar todas as Futuretarefas de envio ExecutorServicee verificação do status com o bloqueio de chamada get()no Futureobjeto

Dê uma olhada nas perguntas relacionadas ao SE:

Como esperar por um thread que gera seu próprio thread?

Executores: como esperar de forma síncrona até que todas as tarefas sejam concluídas se as tarefas forem criadas recursivamente?

Ravindra babu
fonte
2

Eu sugeriria olhar o javadoc para a classe Thread .

Você tem vários mecanismos para manipulação de threads.

  • Seu encadeamento principal poderia join()os três encadeamentos em série e não prosseguiria até que todos os três terminassem.

  • Pesquise o estado da rosca gerada em intervalos.

  • Coloque todos os threads gerados em um separado ThreadGroupe faça uma sondagem activeCount()no ThreadGroupe espere que ele chegue a 0.

  • Configure um tipo de interface de retorno de chamada ou ouvinte personalizado para comunicação entre threads.

Tenho certeza de que há muitas outras maneiras pelas quais ainda sinto falta.

digitaljoel
fonte
1

Aqui está uma solução simples, curta, fácil de entender e que funciona perfeitamente para mim. Eu precisava desenhar na tela quando outro segmento terminar; mas não foi possível porque o segmento principal tem controle da tela. Assim:

(1) Criei a variável global: boolean end1 = false; o thread define como true ao final. Isso é captado no mainthread pelo loop "postDelayed", no qual é respondido.

(2) Meu tópico contém:

void myThread() {
    end1 = false;
    new CountDownTimer(((60000, 1000) { // milliseconds for onFinish, onTick
        public void onFinish()
        {
            // do stuff here once at end of time.
            end1 = true; // signal that the thread has ended.
        }
        public void onTick(long millisUntilFinished)
        {
          // do stuff here repeatedly.
        }
    }.start();

}

(3) Felizmente, "postDelayed" é executado no thread principal, então é aí que o outro thread é verificado uma vez a cada segundo. Quando o outro encadeamento termina, isso pode começar o que queremos fazer a seguir.

Handler h1 = new Handler();

private void checkThread() {
   h1.postDelayed(new Runnable() {
      public void run() {
         if (end1)
            // resond to the second thread ending here.
         else
            h1.postDelayed(this, 1000);
      }
   }, 1000);
}

(4) Finalmente, inicie a coisa toda executando em algum lugar do seu código chamando:

void startThread()
{
   myThread();
   checkThread();
}
DreamMaster Pro
fonte
1

Eu acho que a maneira mais fácil é usar a ThreadPoolExecutorclasse.

  1. Possui uma fila e você pode definir quantos threads devem trabalhar em paralelo.
  2. Possui bons métodos de retorno de chamada:

Métodos de gancho

Essa classe fornece métodos beforeExecute(java.lang.Thread, java.lang.Runnable)e substituíveis protegidos afterExecute(java.lang.Runnable, java.lang.Throwable)chamados antes e após a execução de cada tarefa. Eles podem ser usados ​​para manipular o ambiente de execução; por exemplo, reinicializando ThreadLocals, reunindo estatísticas ou adicionando entradas de log. Além disso, o método terminated()pode ser substituído para executar qualquer processamento especial que precise ser feito depois que o Executor for totalmente finalizado.

que é exatamente o que precisamos. Substituiremos afterExecute()para obter retornos de chamada após cada thread ser concluído e substituiremosterminated() para saber quando todos os threads forem concluídos.

Então aqui está o que você deve fazer

  1. Crie um executor:

    private ThreadPoolExecutor executor;
    private int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();    
    
    
    
    private void initExecutor() {
    
    executor = new ThreadPoolExecutor(
            NUMBER_OF_CORES * 2,  //core pool size
            NUMBER_OF_CORES * 2, //max pool size
            60L, //keep aive time
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>()
    ) {
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
                //Yet another thread is finished:
                informUiAboutProgress(executor.getCompletedTaskCount(), listOfUrisToProcess.size());
            }
        }
    
    };
    
        @Override
        protected void terminated() {
            super.terminated();
            informUiThatWeAreDone();
        }
    
    }
  2. E inicie seus tópicos:

    private void startTheWork(){
        for (Uri uri : listOfUrisToProcess) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    doSomeHeavyWork(uri);
                }
            });
        }
        executor.shutdown(); //call it when you won't add jobs anymore 
    }

O método interno informUiThatWeAreDone();faz o que for necessário quando todos os threads estiverem concluídos, por exemplo, atualizar a interface do usuário.

NOTA: Não se esqueça de usar synchronizedmétodos, pois você faz seu trabalho em paralelo e MUITO CUIDADO se decidir chamar o synchronizedmétodo de outro synchronizedmétodo! Isso geralmente leva a impasses

Espero que isto ajude!

Kirill Karmazin
fonte
0

Você também pode usar o SwingWorker, que possui suporte interno para alterações de propriedades. Consulte addPropertyChangeListener () ou o método get () para obter um exemplo de ouvinte de mudança de estado.

akarnokd
fonte
0

Veja a documentação Java da classe Thread. Você pode verificar o estado do thread. Se você colocar os três threads nas variáveis ​​de membro, os três threads poderão ler os estados um do outro.

Você precisa ter um pouco de cuidado, pois pode causar condições de corrida entre os threads. Apenas tente evitar lógica complicada com base no estado dos outros threads. Definitivamente, evite vários threads gravando nas mesmas variáveis.

Don Kirkby
fonte