[Ajuda] Sincronizar Duas Threads!

6 respostas
Nicolas_Fernandes

Olá, galerinha do GUJ!
Tudo beleza?

Estou com um problema aqui…
Bom, eu tenho uma classe ClienteSplash que será uma telinha Splash que criei, bem básica:

public class ClienteSplash extends JDialog
{
    public ClienteSplash(String anCliente)
    {
        setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        setModal(true);
        iniciarComponentes(anCliente);
    }

    private void iniciarComponentes(String anCliente)
    {
        JPanel anPanel = new JPanel();

        anPanel.setPreferredSize(new Dimension(300, 100));
        anPanel.setBorder(null);

        JLabel anLabel = new JLabel(String.format("Aguarde, criando a base de dados para %s...", anCliente));
        anLabel.setFont(new Font("Tahoma", 0, 11));
        
        anPanel.add(anLabel);
        setContentPane(anPanel);
        pack();
        setLocationRelativeTo(null);
    }

Daí beleza… Eu quero que essa tela seja chamada durante a execução de uma determinada operação na minha base de dados, que consume quase 8 segundos (criar um novo banco de dados).
Estou fazendo algo assim:

// Esta é a operação que deve ser executada no banco de dados.
Runnable firstRunnable = new Runnable()
{
   public void run()
   {
      try
      {
         anModelo.criarBancoCliente(anCliente);
      }
      catch (Exception err)
      {
         err.printStackTrace();
      }
   }
};

// Aqui representa a tela Splash.
Runnable secondRunnable = new Runnable()
{
   final ClienteSplash anSplash = new ClienteSplash(cf.getDados().get("nome").toString());
   public void run()
   {
      anSplash.setVisible(true);
   }
   @Override public void finalize() throws Throwable
   {
      anSplash.dispose();
   }
};

                
Thread firstThread = new Thread(firstRunnable); // Criar a base de dados.
Thread secondThread = new Thread(secondRunnable); // Mostrar a tela splash.

firstThread.start();  // Inicia operação de criar a base de dados...
secondThread.start(); // Inicia a tela splash...
Thread.sleep(8000); // Espera 8 segundos...
secondThread.interrupt(); // Some com a tela splash...
firstThread.interrupt(); // E termina a operação.

Beleza, e vem o problema: As duas threads não estão sincronizadas. Eu mando executar a primeira e, em seguida, mostrar a tela splash. Aqui tem um problema: ao invés de mostrar os componentes, mostra um quadro vazio!

Daí tá… Mostra a tela splash e tudo o mais, espera os 8 segundos… Mas a tela Splash não tá fechando depois dos 8 segundos. O registro é adicionado na tabela, mas a tela não fecha de jeito nenhum.

Alguém tem ideia de como solucionar isso?

Valeu, galera, grande abraço!

6 Respostas

rmendes08

Você não precisa de threads separadas para isso. Na mesma thread que você cria o banco de dados você deve chamar o splash antes de iniciar a criação do banco, e assim que o banco for criado você esconde a splash screen. Não precisa de threads separadas não.

ViniGodoy

Ou seja, fica assim:

Runnable onlyRunnable = new Runnable()  
 {  
   final ClienteSplash anSplash = new ClienteSplash(cf.getDados().get("nome").toString()); 

    public void run()  
    {  
       try  
       {  
          EventQueue.invokeLater(new Runnable() {
              public void run() {
                    anSplash.setVisible(true);
              }
          });    

          anModelo.criarBancoCliente(anCliente);  

          EventQueue.invokeLater(new Runnable() {
              public void run() {
                    anSplash.setVisible(false);
              }
          });    
       }  
       catch (Exception err)  
      {  
          err.printStackTrace();  
      }  
    }  
 };

Lembre-se que para manipular componentes do Swing de outras threads, é necessário empilhar um pedido ao Swing. Por isso a necessidade da chamada do EventQueue.invokeLater.

Nicolas_Fernandes

Hmm… Ótimo, vou testar mais tarde e aviso vocês, qualquer coisa!
Então quer dizer que, para fazer um empilhamento de processos, eu devo chamar um EventQueuer para cada um?
Por exemplo…

EventQueue.invokeLater(new Runnable() {
   public void run() 
   {
      Classe.executarPrimeiroMetodo();
   }
});

EventQueue.invokeLater(new Runnable() {
   public void run() 
   {
      Classe.executarSegundoMetodo();
   }
});

EventQueue.invokeLater(new Runnable() {
   public void run() 
   {
      Classe.executarTerceiroMetodo();
   }
});

Neste caso, ele chama cada um dos processos nessa ordem que invoquei?

ViniGodoy

Se você vai rodar os três na sequência, pode colocar todos os 3 dentro do Runnable.

Note que isso aí roda dentro da thread do Swing. O seu processamento pesado não deve ser jogado pra EventQueue. Só ações que solicitam pintura e atualização de status.

Nicolas_Fernandes

ViniGodoy:
Se você vai rodar os três na sequência, pode colocar todos os 3 dentro do Runnable.

Note que isso aí roda dentro da thread do Swing. O seu processamento pesado não deve ser jogado pra EventQueue. Só ações que solicitam pintura e atualização de status.

Então digamos que o processamento de criar um novo Banco de Dados e suas respectivas tabelas é um processamento pesado. Há algum meio melhor de fazer o processamento: Mostra Splash -> Cria Banco de Dados -> Esconde Splash? Ou do jeito que você postou, através de um único Runnable, fica legal?

ViniGodoy

Fica sim. O que esse runabble faz é:

  1. Empilhar um pedido pra o Swing exibir o splash;
  2. Iniciar o processamento pesado. --> Em paralelo, a thread do Swing processa a sua fila de mensagens e chega na do Runnable, exibindo então o splash;
  3. Pedir para o swing esconder a janela. -> Em paralelo, a thread do Swing fecha a janela.
Criado 18 de outubro de 2010
Ultima resposta 18 de out. de 2010
Respostas 6
Participantes 3