Como fazer um encadeamento Java aguardar a saída de outro encadeamento?


128

Estou criando um aplicativo Java com um thread de lógica de aplicativo e um thread de acesso ao banco de dados. Ambos persistem por toda a vida útil do aplicativo e precisam estar em execução ao mesmo tempo (um fala com o servidor, outro com o usuário; quando o aplicativo é totalmente iniciado, preciso que ambos trabalhem).

No entanto, na inicialização, preciso garantir que, inicialmente, o encadeamento do aplicativo aguarde até que o encadeamento db esteja pronto (atualmente determinado pela pesquisa de um método personalizado dbthread.isReady()). Eu não me importaria se o segmento de aplicativo bloqueie até que o segmento db esteja pronto.

Thread.join() não parece uma solução - o encadeamento db só sai no encerramento do aplicativo.

while (!dbthread.isReady()) {} funciona, mas o loop vazio consome muitos ciclos do processador.

Alguma outra ideia? Obrigado.

Respostas:


128

Eu realmente recomendaria que você seguisse um tutorial como o Java Concurrency da Sun antes de começar no mundo mágico do multithreading.

Existem também vários livros bons (google para "Programação simultânea em Java", "Java simultaneidade na prática".

Para obter sua resposta:

No seu código que deve aguardar o dbThread, você deve ter algo como isto:

//do some work
synchronized(objectYouNeedToLockOn){
    while (!dbThread.isReady()){
        objectYouNeedToLockOn.wait();
    }
}
//continue with work after dbThread is ready

No dbThreadmétodo do seu, você precisaria fazer algo como isto:

//do db work
synchronized(objectYouNeedToLockOn){
    //set ready flag to true (so isReady returns true)
    ready = true;
    objectYouNeedToLockOn.notifyAll();
}
//end thread run method here

O que objectYouNeedToLockOneu estou usando nesses exemplos é preferencialmente o objeto que você precisa manipular simultaneamente a partir de cada thread, ou você pode criar um separado Objectpara esse fim (eu não recomendaria tornar os métodos sincronizados):

private final Object lock = new Object();
//now use lock in your synchronized blocks

Para aumentar sua compreensão:
Existem outras maneiras (às vezes melhores) de fazer o que foi descrito acima, por exemplo, com CountdownLatches, etc. Desde o Java 5, existem muitas classes de simultaneidade bacanas no java.util.concurrentpacote e nos subpacotes. Você realmente precisa encontrar material on-line para conhecer a simultaneidade ou obter um bom livro.


Nem todo código de thread pode ser bem integrado aos objetos, se não estiver errado. Portanto, não acho que o uso da sincronização de objetos seja uma boa maneira de implementar esse trabalho relacionado a threads.
User1914692

@ user1914692: Não sabe ao certo quais são as armadilhas ao usar a abordagem acima - importa explicar melhor?
Piskvor saiu do prédio 27/02

1
@Piskvor: Desculpe, escrevi isso há muito tempo e quase esqueci o que estava em minha mente. Talvez eu apenas quisesse usar melhor o bloqueio, em vez da sincronização de objetos, sendo o último uma forma simplificada do primeiro.
precisa saber é o seguinte

Eu não entendo como isso funciona. Se o thread aestiver aguardando um objeto, synchronised(object)como outro thread pode passar synchronized(object)para chamar object.notifyAll()? No meu programa, tudo ficou preso em synchronozedblocos.
Tomáš Zato - Restabelece Monica

@ TomášZato, o primeiro thread chama object.wait()efetivamente desbloqueando a trava naquele objeto. Quando o segundo thread "sai" de seu bloco sincronizado, os outros objetos são liberados do waitmétodo e obtêm novamente o bloqueio naquele momento.
Rogerdpack

140

Use um CountDownLatch com um contador de 1.

CountDownLatch latch = new CountDownLatch(1);

Agora, no tópico do aplicativo,

latch.await();

No encadeamento db, depois de concluir, faça -

latch.countDown();

4
Eu realmente gosto dessa solução por sua simplicidade, embora possa ser mais difícil entender o significado do código à primeira vista.
lethal-guitar

3
Esse uso exige que você refaça a trava quando se acostumar. Para obter um uso semelhante a um Waitable Event no Windows, tente um BooleanLatch ou CountDownLatch redefinível : docs.oracle.com/javase/7/docs/api/java/java/util/concurrent/locks/… stackoverflow.com/questions / 6595835 /…
phyatt 07/04

1
Oi, se eu chamar um método assíncrono que deveria disparar um evento como tal: 1) asyncFunc (); 2) latch.await (); Então eu contagem regressiva na função de manipulação de eventos, uma vez que é recebida. Como posso garantir que o evento não será tratado ANTES que latch.await () seja chamado? Gostaria de evitar a preempção entre as linhas 1 e 2. Obrigado.
NioX5199

1
Para evitar esperar para sempre em caso de erros, coloque countDown()um finally{}bloco
Daniel Alder

23

Requerimento ::

  1. Aguardar a execução do próximo encadeamento até a conclusão anterior.
  2. O próximo encadeamento não deve iniciar até o encadeamento anterior parar, independentemente do consumo de tempo.
  3. Deve ser simples e fácil de usar.

Responda ::

@Veja java.util.concurrent.Future.get () doc.

future.get () Aguarda, se necessário, a conclusão da computação e recupera seu resultado.

Tarefa concluída!! Veja o exemplo abaixo

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.Test;

public class ThreadTest {

    public void print(String m) {
        System.out.println(m);
    }

    public class One implements Callable<Integer> {

        public Integer call() throws Exception {
            print("One...");
            Thread.sleep(6000);
            print("One!!");
            return 100;
        }
    }

    public class Two implements Callable<String> {

        public String call() throws Exception {
            print("Two...");
            Thread.sleep(1000);
            print("Two!!");
            return "Done";
        }
    }

    public class Three implements Callable<Boolean> {

        public Boolean call() throws Exception {
            print("Three...");
            Thread.sleep(2000);
            print("Three!!");
            return true;
        }
    }

    /**
     * @See java.util.concurrent.Future.get() doc
     *      <p>
     *      Waits if necessary for the computation to complete, and then
     *      retrieves its result.
     */
    @Test
    public void poolRun() throws InterruptedException, ExecutionException {
        int n = 3;
        // Build a fixed number of thread pool
        ExecutorService pool = Executors.newFixedThreadPool(n);
        // Wait until One finishes it's task.
        pool.submit(new One()).get();
        // Wait until Two finishes it's task.
        pool.submit(new Two()).get();
        // Wait until Three finishes it's task.
        pool.submit(new Three()).get();
        pool.shutdown();
    }
}

Saída deste programa:

One...
One!!
Two...
Two!!
Three...
Three!!

Você pode ver que demora 6 segundos antes de concluir sua tarefa, que é maior que o outro encadeamento. Então Future.get () aguarda até que a tarefa seja concluída.

Se você não usar future.get (), ele não espera para terminar e executa o consumo de tempo baseado.

Boa sorte com a concorrência Java.


Obrigado pela sua resposta! Eu usei CountdownLatches, mas a sua é uma abordagem muito mais flexível.
Piskvor saiu do prédio 18/01/12

9

Muitas respostas corretas, mas sem um exemplo simples. Aqui está uma maneira fácil e simples de como usar CountDownLatch:

//inside your currentThread.. lets call it Thread_Main
//1
final CountDownLatch latch = new CountDownLatch(1);

//2
// launch thread#2
new Thread(new Runnable() {
    @Override
    public void run() {
        //4
        //do your logic here in thread#2

        //then release the lock
        //5
        latch.countDown();
    }
}).start();

try {
    //3 this method will block the thread of latch untill its released later from thread#2
    latch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}

//6
// You reach here after  latch.countDown() is called from thread#2

8
public class ThreadEvent {

    private final Object lock = new Object();

    public void signal() {
        synchronized (lock) {
            lock.notify();
        }
    }

    public void await() throws InterruptedException {
        synchronized (lock) {
            lock.wait();
        }
    }
}

Use esta classe assim:

Crie um ThreadEvent:

ThreadEvent resultsReady = new ThreadEvent();

No método, isso está aguardando resultados:

resultsReady.await();

E no método que está criando os resultados após a criação de todos os resultados:

resultsReady.signal();

EDITAR:

(Desculpe por editar esta postagem, mas esse código tem uma condição de corrida muito ruim e eu não tenho reputação suficiente para comentar)

Você só pode usar isso se tiver 100% de certeza de que o sinal () é chamado após aguardar (). Esse é o grande motivo pelo qual você não pode usar objetos Java como, por exemplo, Windows Events.

O se o código for executado nesta ordem:

Thread 1: resultsReady.signal();
Thread 2: resultsReady.await();

então o thread 2 esperará para sempre . Isso ocorre porque Object.notify () ativa apenas um dos threads em execução no momento. Um encadeamento aguardando mais tarde não é ativado. Isso é muito diferente de como eu espero que os eventos funcionem, onde um evento é sinalizado até que a) espere ou b) reinicie explicitamente.

Nota: Na maioria das vezes, você deve usar notifyAll (), mas isso não é relevante para o problema "espere para sempre" acima.


7

Experimente a classe CountDownLatch fora do java.util.concurrentpacote, que fornece mecanismos de sincronização de nível superior, que são muito menos propensos a erros do que qualquer material de baixo nível.


6

Você pode fazer isso usando um objeto Exchanger compartilhado entre os dois threads:

private Exchanger<String> myDataExchanger = new Exchanger<String>();

// Wait for thread's output
String data;
try {
  data = myDataExchanger.exchange("");
} catch (InterruptedException e1) {
  // Handle Exceptions
}

E no segundo segmento:

try {
    myDataExchanger.exchange(data)
} catch (InterruptedException e) {

}

Como já foi dito, não use esse código alegre e apenas copie e cole. Faça algumas leituras primeiro.


4

A interface Futuro dojava.lang.concurrent pacote foi projetada para fornecer acesso aos resultados calculados em outro encadeamento.

Dê uma olhada no FutureTask e no ExecutorService para obter uma maneira pronta de fazer esse tipo de coisa.

Eu recomendo fortemente a leitura de Java Concurrency In Practice para qualquer pessoa interessada em simultaneidade e multithreading. Obviamente, ele se concentra em Java, mas há muita carne para quem trabalha em outras linguagens também.


2

Se você quiser algo rápido e sujo, basta adicionar uma chamada Thread.sleep () dentro do seu loop while. Se a biblioteca do banco de dados é algo que você não pode mudar, não há outra solução fácil. A pesquisa no banco de dados até que esteja pronto com um período de espera não prejudica o desempenho.

while (!dbthread.isReady()) {
  Thread.sleep(250);
}

Dificilmente algo que você poderia chamar de código elegante, mas realiza o trabalho.

Caso você possa modificar o código do banco de dados, é melhor usar um mutex como proposto em outras respostas.


3
Isso é praticamente apenas ocupado esperando. O uso de construções dos pacotes util.concurrent do Java 5 deve ser o caminho a seguir. O stackoverflow.com/questions/289434/… me parece a melhor solução por enquanto.
Cem Catikkas

Ele está ocupado aguardando, mas se for necessário apenas nesse local específico e se não houver acesso à biblioteca db, o que mais você pode fazer? Espera ocupado não é necessariamente mal
Mario Ortegón

2

Isso se aplica a todos os idiomas:

Você deseja ter um modelo de evento / ouvinte. Você cria um ouvinte para aguardar um evento específico. O evento seria criado (ou sinalizado) no seu thread de trabalho. Isso bloqueará o encadeamento até que o sinal seja recebido em vez de pesquisar constantemente para verificar se uma condição é atendida, como a solução que você possui atualmente.

Sua situação é uma das causas mais comuns de conflitos - certifique-se de sinalizar o outro encadeamento, independentemente dos erros que possam ter ocorrido. Exemplo - se seu aplicativo lança uma exceção - e nunca chama o método para sinalizar ao outro que as coisas foram concluídas. Isso fará com que o outro thread nunca 'acorde'.

Sugiro que você analise os conceitos de uso de eventos e manipuladores de eventos para entender melhor esse paradigma antes de implementar seu caso.

Como alternativa, você pode usar uma chamada de função de bloqueio usando um mutex, o que fará com que o encadeamento aguarde a liberação do recurso. Para fazer isso, você precisa de uma boa sincronização de threads, como:

Thread-A Locks lock-a
Run thread-B
Thread-B waits for lock-a
Thread-A unlocks lock-a (causing Thread-B to continue)
Thread-A waits for lock-b 
Thread-B completes and unlocks lock-b

2

Você pode ler de uma fila de bloqueio em um thread e gravá-lo em outro thread.


1

Desde a

  1. join() foi descartado
  2. você já está usando CountDownLatch e
  3. Future.get () já foi proposto por outros especialistas,

Você pode considerar outras alternativas:

  1. invokeAll fromExecutorService

    invokeAll(Collection<? extends Callable<T>> tasks)

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

  2. ForkJoinPool ou newWorkStealingPool de Executors(desde o lançamento do Java 8)

    Cria um pool de threads que rouba trabalho, usando todos os processadores disponíveis como seu nível de paralelismo de destino.


-1

insira a descrição da imagem aqui

Esta ideia pode ser aplicada? Se você usa CountdownLatches ou Semaphores funciona perfeitamente, mas se você está procurando a resposta mais fácil para uma entrevista, acho que isso pode ser aplicado.


1
Como isso é bom para uma entrevista, mas não para o código real?
Piskvor saiu do prédio

Como, neste caso, está sendo executado seqüencialmente, uma espera por outra. A melhor solução poderia ser usar semáforos, porque usando CountdownLatches, que é a melhor resposta dada aqui, o Thread nunca entra em suspensão, o que significa o uso de ciclos de CPU.
Franco

Mas a questão não era "executá-los sequencialmente". Vou editar a pergunta para deixar isso mais claro: o encadeamento da GUI aguarda até que o banco de dados esteja pronto e, em seguida, ambos são executados simultaneamente pelo resto da execução do aplicativo: o encadeamento da GUI envia comandos para o encadeamento do banco de dados e lê os resultados. (E, novamente: qual é o sentido do código que pode ser usado em uma entrevista, mas não no código real? A maioria dos entrevistadores técnicos que eu conheci possuía experiência em código e faria a mesma pergunta; além disso, eu precisava disso para um aplicativo real Eu estava escrevendo na época, não para me dedicar à lição de casa)
Piskvor saiu do prédio

1
Assim. Este é um problema do consumidor produtor usando semáforos. Vou tentar dar um exemplo
Franco

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.