Threads + JDialog - Dúvidas de como usar corretamente

Estou com algumas dúvidas relacionadas às Threads + JDialog, vou explicar algumas situações e gostaria de saber como aplicar para que funcione da maneira que eu preciso.

Atualmente tenho o seguinte:

Possuo um JFrame que contém dois JPanel, um deles é a barra de menu superior que possui alguns botões e o outro JPanel é o que eu chamo de desktop, pois é nele que vou atribuindo as telas específicas quando o usuário clica nos botões específicos, no primeiro JPanel que eu citei, que é a barra de menu superior e que está presente durante toda a aplicação e em toda e qualquer outra tela, possuo algumas Threads (Runnable) que fazem um “monitoramento” em saídas específicas de um CLP com o qual a minha aplicação se comunica, então dependendo do status de algumas dessas saídas que estão sendo monitoradas preciso abrir um JDialog com algumas informações, levando em consideração esse caso segue alguns pontos:

  - Usando o JDialog com o parâmetro modal = true quando o mesmo ficar visível toda aplicação abaixo dele fica bloqueada, até mesmo o JFrame principal, então o usuário não pode fechar a aplicação sem que o JDialog seja "liberado".

 - Usando o JDialog com o parâmetro modal = false e o setAlwaysOnTop(true) quando o mesmo fica visível toda a aplicação abaixo dele fica liberada, sendo que o usuário pode acessar outras telas clicando nos botões da tela abaixo do JDialog sem problemas nenhum.

Resumindo a minha necessidade, eu gostaria de uma junção dos dois pontos que citei acima, mais ou menos assim:

- A thread chama o JDialog e este fica visível bloqueando o acesso ao JPanel (desktop) que citei acima, mas o JPanel de menu superior e o JFrame ficam disponíveis, principalmente o JFrame, para que o usuário possa fechar a aplicação se necessário, sem ter que liberar o JDialog, como posso fazer isso?

Então, agora além desta dúvida, ainda tenho outra, neste JDialog que é aberto, nele eu tenho outra thread (Runnable) que também fica “monitorando” saídas específicas de um CLP, para que esse JDialog possa ser liberado/fechado certas saídas que estão sendo monitoradas precisam estar acionadas, senão o JDialog não pode sumir, além disso tenho neste JDialog alguns labels com a descrição destas saídas e um icon para quando elas estiverem liberadas e outro para quando está bloqueadas, esses icons são setados pela thread de acordo com a leitura que a mesma vai realizando no CLP, o que acontece é que ás vezes parece que que thread trava ou “se perde”, pois o JDialog e seus componentes ficam se modificando sem sentido e até mesmo já aconteceu de travar e não permitir mais sair, gostaria de saber o que posso estar fazendo de errado e o que seria o ideal neste caso.

Se caso não for possível entender da maneira que expliquei me coloco a disposição para eclarecer o que for necessário.

Desde já agradeço.

Fico no aguardo.

Att.

Quantos CLPs você tem e quantas Threads monitoram ele?

Um CLP e atualmente, no JPanel que citei, tem sete threads e nos JDialog (são quatro) que abrem em virtude do que acontecer no monitoramento dessas threads, tenho uma thread em cada, que fica monitorando para liberar ou não o “fechar” do JDialog.

Sugiro que tenha somente uma Thread para monitorar o CLP e crie uma estrutura de Listener para ser notificado sobre as mudanças no estado do CLP.

Já tinha pensado nesta questão de juntar e fazer somente uma thread, vou implementar isto, obrigada! Essa estrutura de Listener, poderias me explicar melhor?

E quanto aos JDialog, teria alguma ideia do que posso fazer?

Dê uma estudada no padrão de projeto Observer (também chamado de Listener).

Você já usou no seu dia-a-dia, apenas não percebeu.
Quando você quer tratar por exemplo o clique em um botão, você adiciona um ActionListener ao botão e o botão avisa esse ActionListener quando o botão é acionado, assim, toda classe que precisar ser notificada quando aquele botão é acionado, deve adicionar um ActionListener ao botão.

No seu caso você não vai adicionar listeners à um botão e sim à sua classe que monitora o CLP.
Sem ver o seu código, não tenho como te orientar da melhor forma, mas você pode por exemplo:

  • Criar uma classe CLPMonitor, que é quem vai monitorar o CLP (hoje você faz isso em várias Threads, agora vai fazer isso em uma só, encapsulada nessa classe);

  • Criar uma interface chamada CLPListener, nessa interface defina um método para cada evento que possa ser disparado pela classe CLPMonitor;

  • Crie uma classe CLPEvent, para encapsular as informações geradas à cada evento, esse CLPEvent você passa como parâmetro aos métodos da interface CLPListener;

Com essa estrutura acima, basta você adicionar CLPListeners ao CLPMonitor de forma que o CLPMonitor chame os métodos do CLPListener a medida que o monitor percebe mudanças no CLP.

Olá staroski,

Vou começar hoje implementar essa estrutura de Listener que me sugeriu, mas antes quero tirar umas dúvidas à respeito:

  1. A classe CLPMonitor irá implementar a interface CLPListener, então na classe além de implementar todos os métodos abstrator da interface eu terei ainda uma única thread que vai monitorar o CLP e dependendo da informação recebida irá disparar um desses métodos, isto?

  2. Quanto à classe CLPEvent, que você citou que deve ser passada como parâmetro aos métodos da interface, não entendi muito bem, seria por exemplo, a thread recebeu uma informação de erro do CLP então um dos métodos no CLPMonitor(método abstrato da interface) será disparado e neste método será usado o CLPEvent que seria “o evento que acontece” quando o tal erro do CLP foi identificado?

  3. Se não for pedir muito, poderia me exemplificar como ficaria a comunicação entre essas classes/interface, levando em consideração o exemplo que citei acima, de um erro qualquer recebido do CLP e que irá disparar um dos métodos para abrir uma janela sobre o erro, algo assim.

Enquanto isso vou implemantando em um geral a classe CLPMonitor e a interface.

Grata.

Não, a classe CLPMonitor terá um método addCLPListener para você poder adicionar listeners à ela.

O que você monitora no CLP? A ideia é essa classe encapsular os dados lidos do CLP.

Fica complicado eu fazer o exemplo sendo que você não postou o código que você tem.
Não sei o que você monitora nos CLPs.

Eu monitoro variáveis/bits específicos, por exemplo, uma variável é 4099 e o bit é 9 essa variável e bit em questão retornam se o equipamento está em falha, tem diversas outras variáveis com seus próprios bits, retornando true e false dependendo da situação. Então através de uma biblioteca modbus externa faço leitura e escrita, exemplo:

if(modbus.readBoolean(4099, 9) {
     // máquina em falha
     // abre tela informativa
}

Uso neste sentido. Então atualmente eu tenho threads que ficam rodando e dentro delas tenho o verificador if monitorando as variáveis/bits conforme exemplo acima, e quando entra no if realiza determinada coisa, na maioria somente faz reset em algumas variáveis/bits (writeBoolean(variável, bit, false)) e em outras abre um JDialog confome citei.

  • Como seria mais ou menos esse método addCLPListener?

  • O CLPEvent vai conter o que for lido no CLP?

public class CLPMonitor {

    private List<CLPListener> listeners = new LinkedList<>();

    public void addCLPListener(CLPListener listener) {
        listeners.add(listener);
    }

    public void iniciar() {
        // aqui você dispara a Thread pra monitorar o CLP
    }
}

No código que monitora o CLP você fará algo mais ou menos assim:

if (modbus.readBoolean(4099, 9) {
    for (CLPListener listener : listeners) {
        listener.maquinaEmFalha(new CLPEvent(4099, 9));
    }
}

Vou te mandar o que tenho até o momento, para que você verifique se estou fazendo corretamente:

Interface CLPListener:

public interface CLPListener {
   // (4099, 9)
   public void generalEmergency(CLPEvent event);
}

Classe CLPMonitor:

public class CLPMonitor {

    private Protocol protocol;
    private List<CLPListener> listeners = new LinkedList<CLPListener>();

    public CLPMonitor() {
        protocol = ActionMachineBuilder.getConnection();
        clpMonitor();
    }

    public void addCLPListener(CLPListener listener) {
        listeners.add(listener);
    }

    public void clpMonitor() {
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    protocol.connect();

                    while (true) {
                        if (protocol.readBoolean(4099, 9))  {
                            for (CLPListener listener : listeners) {
                                listener.generalEmergency(new CLPEvent(4099, 9));
                            }
                        }
                    }
                } catch (Exception e) {
                    // ignored
                    protocol.disconnect();
                }
            }
        });
        thread.start();
    }
}

Classe CLPEvent:

public class CLPEvent {
    
    private int variable;
    private int bit;
   
    public CLPEvent(int variable, int bit) {
        this.variable = variable;
        this.bit = bit;
    }
}

Até aqui tudo certo?

Agora quanto ao “evento” que deve ser disparado, por exemplo, a abertura de um JDialog informando a situação de máquina em falha, deve ser feito na classe que irá instanciar o CLPMonitor? E essa classe irá implementar os métodos da interface CLPListener, ou essa implementação já é na classe CLPEvent?

No meu caso eu instanciaria a classe CLPMonitor no meu JPanel fixo de menu, para que fique monitorando durante toda a execução da aplicação.

Em princípio sim, eu renomearia o método public void clpMonitor() para public void start(), fica mais legível.

Não precisa exatamente ser a classe que instanciou o CLPMonitor, não sei como está sua implementação.
O método addCLPMonitorListener permite que qualquer implementação de CLPMonitorListener se registre nele para ser avisado quando algo acontece.

O CLPEvent só encapsula os dados obtidos pelo CLP, o CLPEvent não implementa a interface CLPListener.

OK, então no seu JPanel após inicializar o CLPMonitor (ou receber uma instância dele por parâmetro, não sei como está implementado) você fará isso:

meuObjetoClpMonitor.addCLPListener(new CLPListener() {

    public void generalEmergency(CLPEvent event) {
        // aqui você chama algum método pra fazer algo quando a mensagem do CLP chegar
    }
});

Percebe como o uso de listener simplifica a implementação?

Antes você tinha meia dúzia de threads perguntando o status do CLP o tempo todo.

Agora você tem somente uma thread que faz isso e que avisa todos os objetos interessados.

Quem quer receber avisos do CLPMonitor, só precisa registrar um CLPListener.

Ah sim, muito obrigada, agora ficou claro como vai ser o funcionamento, não há uma implementação da interface, eu ignorei completamente o conceito do Listener em si, desculpe.

Tranquilo, agora uma dúvida quanto ao monitoramento, por exemplo, no meu JPanel que comentei que roda em toda a aplicação eu vou ter o seguinte:

CLPMonitor clpMonitor = new CLPMonitor();
clpMonitor.start();
clpMonitor.addCLPListener(new CLPListener() {
     @Override
     public void generalEmergency(CLPEvent event) {
         // método à ser chamado com o que deve ser feito   
     }
});

Esse código irá startar a thread de monitoramento na classe CLPMonitor e o Listener fica escutando para então disparar o trecho de código necessário em cada situação que o CLP fornecer. Agora, por exemplo, se eu tiver um outra classe e nela eu preciso também monitorar algo no CLP, mas é somente uma ou duas situações específicas para aquele processo que tem naquela classe, se eu fizer nela o mesmo trecho de código acima eu estarei startando a thread novamente e vou ter ela rodando duas vezes, estou certa?
Então se eu somente fizer:

CLPMonitor clpMonitor = new CLPMonitor();
clpMonitor.addCLPListener(new CLPListener() {
     @Override
     public void generalEmergency(CLPEvent event) {
         // método à ser chamado com o que deve ser feito   
     }
});

Já vai funcionar né? Não preciso chamar o método clpMonitor.start()? E aí neste caso eu só implemento o método que eu precisar no Listener?

Nas outras classes você não vai chamar o método start(), você vai somente passar o mesmo objeto clpMonitor e adicionar mais um CLPListener ao objeto.

Conforme eu fiz acima então? Crio um novo objeto CLPMonitor e adiciono o Listener e aí implemento os métodos que necessito e pronto?

A única diferença é que não vou evocar o método start(), este só será evocado uma vez, isto?

Grata.

Não, ninguém falou em criar um novo objeto CLPMonitor, você vai instanciar o CLPMonitor uma única vez.

O mais simples é você passar o objeto CLPMonitor que você instanciou para elas e aí essas classes fazem o addCLPMonitorListener.

Ok então, irei fazer isto e ver como vai funcionar.

Olá,

Bom, estive implementando o que tratamos aqui no tópico e acabou me surgindo uma dúvida, provavelmente pode ser levada como “boba”, mas asism, conforme citei acima, o que eu uso para monitorar as variáveis/bits na thread do CLPMonitor é o seguinte:

if (protocol.readBoolean(4099, 9))  {
     for (CLPListener listener : listeners) {
         listener.generalEmergency(new CLPEvent(4099, 9));
     }
}

Então, fazendo isto, quando o CLP retornar true na variável 4099 bit 9 o listener vai disparar o evento específico, mas se eu também quiser ficar sabendo quando a variável/bit em questão estiver retornando false? É somente fazer um else nesse if acima e adicionar um listener para disparar um evento específico para quando for false?

Exatamente, para cada situação esperada você cria um método diferente na sua interface CLPListener.

Olá staroski,

Faz tempo que não mexo aqui no tópico porque não havia tido a oportunidade de testar na prática esta implementação, esta semana estou fazendo isto, mas estive me deparando com a seguinte exceção:

Exception in thread "Thread-6" java.util.ConcurrentModificationException
	at java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:966)
	at java.util.LinkedList$ListItr.next(LinkedList.java:888)
	at com.cutplanning.cvl.monitor.CLPMonitor$1.run(CLPMonitor.java:111)
	at java.lang.Thread.run(Thread.java:748)

Dei uma pesquisada, mas não consegui definir uma solução para isto, saberia me dizer o que eu faço para resolver e também o por que desta exceção?

A única coisa que entendi é que o problema é no acesso à LinkedList a qual adiciono os eventos disparados pelo CLP para então poder tratá-los.

Desde já agradeço.