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?
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?
Respostas:
Existem várias maneiras de fazer isso:
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 NotifyingThread
e, 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 ThreadCompleteListener
e 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 notifyOfThreadComplete
mé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 Thread
para NotifyingThread
como a extensão da linha é geralmente desencorajado em novo código. Mas estou codificando para sua pergunta. Se você alterar a NotifyingThread
classe a ser implementada Runnable
, precisará alterar parte do seu código que gerencia os Threads, o que é bastante simples de fazer.
notify
método não dentro do run
método, mas depois dele.
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.
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 Runnable
interface 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 CallbackRunnable
interface é 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.concurrent
se o seu projeto permitir.
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?
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.
Deseja esperar que eles terminem? Nesse caso, use o método Join.
Há também a propriedade isAlive se você quiser apenas verificá-la.
Thread
a start
, o que o encadeamento faz, mas a chamada retorna imediatamente. isAlive
deveria ser um teste simples de flag, mas quando pesquisei no Google o método era native
.
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.
Você também pode usar o Executors
objeto para criar um pool de threads ExecutorService . Em seguida, use o invokeAll
mé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 awaitTermination
para bloquear até que o pool termine de executar. Lembre-se de ligar para shutdown
() quando terminar de adicionar tarefas.
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.
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 docountDown()
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 Future
tarefas de envio ExecutorService
e verificação do status com o bloqueio de chamada get()
no Future
objeto
Dê uma olhada nas perguntas relacionadas ao SE:
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 ThreadGroup
e faça uma sondagem activeCount()
no ThreadGroup
e 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.
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();
}
Eu acho que a maneira mais fácil é usar a ThreadPoolExecutor
classe.
Métodos de gancho
Essa classe fornece métodos
beforeExecute(java.lang.Thread, java.lang.Runnable)
e substituíveis protegidosafterExecute(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étodoterminated()
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
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();
}
}
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 synchronized
métodos, pois você faz seu trabalho em paralelo e MUITO CUIDADO se decidir chamar o synchronized
método de outro synchronized
método! Isso geralmente leva a impasses
Espero que isto ajude!
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.
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.