Nesta aula você conhece a história das interfaces gráficas em Java, do AWT de 1995 ao JavaFX 27 de 2026, entende a regra que todas compartilham (uma única thread desenha a tela) e constrói, em Swing, uma janela de catálogo com tabela, filtro, ordenação e carregamento em segundo plano.
Quando o Java foi lançado, em 1995, a promessa que mais chamou atenção não foi a coleta de lixo nem a portabilidade do bytecode no servidor. Foi a de programas com janelas que rodam em qualquer sistema: os applets dentro do navegador e os aplicativos de desktop que abriam igual no Windows, no Mac e no Solaris. Essa promessa se provou muito mais difícil de cumprir do que parecia, e a história de como o Java tentou cumpri-la explica por que hoje existem três bibliotecas gráficas no ecossistema, e quando usar cada uma.
AWT: janelas emprestadas do sistema (1995)
A primeira biblioteca foi o AWT (Abstract Window Toolkit). A ideia era direta: cada componente Java (Button, TextField, List) tem um par nativo, o peer, que é o botão de verdade do sistema operacional. No Windows, um java.awt.Button é um botão do Windows. No Solaris, um botão do Motif. Os componentes AWT são chamados de pesados (heavyweight): cada um é uma janela nativa.
A vantagem era a aparência exatamente nativa, e o problema também. Como o AWT só podia oferecer o que existia em todos os sistemas, ficou preso ao menor denominador comum. Não havia tabela nem árvore, porque nem todos os sistemas tinham uma. E o mesmo botão tinha tamanhos, fontes e comportamentos sutilmente diferentes em cada sistema, o que deu origem à piada que perseguiu o Java por anos: "write once, debug everywhere".
O AWT não morreu. Ele continua no JDK e é a base de tudo o que veio depois: janelas (Frame), eventos (ActionEvent, MouseEvent), a fila de eventos, o desenho com Graphics2D (a API Java 2D, de 1998) e a leitura de imagens que usamos no upload de fotos. O que ninguém usa mais são os componentes pesados.
Swing: desenhar tudo sozinho (1998)
A resposta veio de uma colaboração entre a Sun e a Netscape, que tinha sua própria biblioteca de interface em Java, a IFC. O resultado foi o Swing, parte das JFC (Java Foundation Classes), incluído no Java 1.2, em 1998. A inversão foi radical: em vez de pedir botões ao sistema, o Swing desenha tudo ele mesmo, pixel a pixel, com o Java 2D. Só a janela de fora (JFrame) é nativa. Os componentes Swing são leves (lightweight).
Desenhar tudo trouxe três consequências:
- Riqueza. Como o Swing não depende do que o sistema oferece, pode ter
JTable,JTree, editores de texto com estilo, abas e divisórias arrastáveis, com o mesmo comportamento em todos os sistemas. - Aparência trocável (pluggable look and feel). O mesmo programa pode se vestir de Metal (o visual Java padrão), de Nimbus (desde 2008), ou imitar o sistema operacional. Bibliotecas como a FlatLaf dão ao Swing um visual moderno e plano, com tema escuro e suporte a telas de alta densidade.
- Arquitetura modelo-visão. Componentes complexos separam os dados (o model) da apresentação (a view). A
JTablenão guarda linha nenhuma: ela pergunta a umTableModelquantas linhas existem e o que vai em cada célula. É o padrão MVC, que você verá em ação daqui a pouco.
No começo, o Swing era lento e parecia "estrangeiro" em todos os sistemas. As JVMs melhoraram, o JIT amadureceu, e hoje ele é rápido e muito estável. Dois dos programas Java mais usados no mundo, as IDEs IntelliJ IDEA e NetBeans, são escritos em Swing. Ele continua no JDK, recebe correções e está em modo de manutenção: não ganha grandes novidades, mas também não vai a lugar nenhum.
SWT: o caminho do meio (2001)
A IBM, ao construir o Eclipse, discordou das duas abordagens e criou o SWT (Standard Widget Toolkit). A ideia era voltar aos componentes nativos, como o AWT, mas sem o menor denominador comum: onde o sistema não oferece um componente, o SWT o emula. O resultado tem aparência realmente nativa, ao custo de ter uma biblioteca nativa diferente por plataforma e de exigir que você libere recursos à mão (dispose()), na contramão do resto do Java. Ele segue vivo no ecossistema Eclipse, e raramente aparece fora dele.
JavaFX: um grafo de cena (2008 → hoje)
Em 2007, a Sun anunciou o JavaFX, com uma linguagem própria, a JavaFX Script, pensada para competir com o Flash e o Silverlight na era das "aplicações ricas para internet". A linguagem não pegou, mas a plataforma sim. O JavaFX 2, de 2011, trocou a linguagem por uma API Java comum e trouxe:
- um grafo de cena (scene graph), a árvore de nós que é desenhada com aceleração por GPU;
- propriedades observáveis e bindings, com os quais a interface se atualiza sozinha quando os dados mudam;
- o FXML, para descrever telas em XML;
- estilos em CSS, animações e gráficos.
O JavaFX passou a vir junto com o JDK da Oracle em 2012, no Java 7 update 6, e virou parte oficial da plataforma no JDK 8, em 2014. Parecia destinado a substituir o Swing. Em 2018, porém, a Oracle o retirou do JDK a partir da versão 11 e o entregou à comunidade como o projeto OpenJFX, conduzido desde então principalmente pela Gluon. A decisão pareceu o fim, e foi um recomeço. Fora do JDK, o JavaFX passou a ser uma dependência como outra qualquer, com lançamentos a cada seis meses, acompanhando o JDK. O JavaFX 27, de setembro de 2026, exige o JDK 25 e trouxe, entre outras coisas, novas media queries no CSS e controles JavaFX na barra de título das janelas.
Qual escolher
| AWT | Swing | SWT | JavaFX | |
|---|---|---|---|---|
| Componentes | nativos (pesados) | desenhados em Java (leves) | nativos + emulados | grafo de cena, GPU |
| Incluído no JDK | sim | sim | não | não (OpenJFX) |
| Aparência | a do sistema | trocável (Metal, Nimbus, FlatLaf...) | a do sistema | CSS |
| Destaques | base de eventos e Java 2D | maturidade, IDEs, estabilidade | Eclipse | bindings, FXML, animação, gráficos |
| Para um projeto novo | não | manutenção, ferramentas internas | só no ecossistema Eclipse | sim |
Na prática, para um projeto novo de desktop em Java, a escolha natural é o JavaFX: é a biblioteca com desenvolvimento ativo, com o modelo de programação mais moderno e com ferramentas como o Scene Builder. O Swing é a escolha certa quando ele já está lá: sistemas legados, plugins de IDE e ferramentas internas simples, em que "vem no JDK, sem dependências" vale muito. Fora do Java puro, vale saber que o Compose Multiplatform, da JetBrains, leva ao desktop o modelo declarativo do Android, e roda na JVM, mas em Kotlin.
Seja qual for a escolha, uma regra vale para todas, e ela é a causa número um de programas de desktop "travados".
A regra de ouro: uma thread desenha a tela
Toda biblioteca gráfica relevante (Swing, JavaFX, SWT, Android, as interfaces do Windows e do macOS) é de thread única. Uma thread especial processa uma fila de eventos, um por vez: um clique, uma tecla, um pedido de repintura. No Swing, ela se chama EDT (Event Dispatch Thread); no JavaFX, JavaFX Application Thread.
Por que não permitir várias threads? Houve tentativas nos anos 1990, e todas tropeçaram no mesmo problema. Os eventos sobem do sistema para os componentes (um clique entra pela janela e desce até o botão), e as mudanças descem da aplicação para o sistema (o código muda o texto, que precisa ser redesenhado). Com travas em cada componente, esses dois fluxos em direções opostas produzem deadlocks quase impossíveis de evitar. É a situação de "O Cadeado e a Memória — synchronized, volatile e o Java Memory Model" em escala industrial. A solução que venceu foi simples: uma thread só, e nenhuma trava.
Isso impõe duas regras.
- Só a thread da interface mexe nos componentes. Criar, alterar e ler, tudo nela.
- A thread da interface nunca espera. Enquanto ela executa o seu código, nada mais acontece na tela: nem repintura, nem cliques, nem o cursor piscando.
A segunda regra é fácil de violar sem perceber. Um Timer do Swing "bate" a cada 50 ms na EDT, como faria uma animação. Medi o maior intervalo entre duas batidas enquanto uma "consulta" de 1,5 s acontecia, primeiro na própria EDT e depois num SwingWorker:
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* Um Timer do Swing "bate" a cada 50 ms na thread de eventos (EDT), como uma animação ou um cursor piscando.
* Medimos o maior intervalo entre duas batidas enquanto uma "consulta lenta" de 1,5 s acontece:
* - na própria EDT (o erro clássico);
* - num SwingWorker (o jeito certo).
*/
void main() throws Exception {
IO.println("consulta na EDT: maior pausa da interface = " + medir(false) + " ms");
IO.println("consulta em SwingWorker: maior pausa da interface = " + medir(true) + " ms");
}
long medir(boolean emSegundoPlano) throws Exception {
var fim = new CountDownLatch(1);
long[] ultimaBatida = {0};
long[] maiorPausa = {0};
SwingUtilities.invokeAndWait(() -> {
var relogio = new Timer(50, _ -> {
long agora = System.nanoTime();
if (ultimaBatida[0] != 0) { // a primeira batida só marca o início
maiorPausa[0] = Math.max(maiorPausa[0], (agora - ultimaBatida[0]) / 1_000_000);
}
ultimaBatida[0] = agora;
});
relogio.start();
// 200 ms depois, "o usuário clica em Recarregar"
var clique = new Timer(200, _ -> {
if (emSegundoPlano) {
new SwingWorker<List<String>, Void>() {
@Override protected List<String> doInBackground() throws Exception {
return consultaLenta(); // roda numa thread de trabalho
}
@Override protected void done() { // volta para a EDT
encerrarDepois(relogio, fim);
}
}.execute();
} else {
consultaLenta(); // trava a EDT por 1,5 s
encerrarDepois(relogio, fim);
}
});
clique.setRepeats(false);
clique.start();
});
fim.await();
return maiorPausa[0];
}
/** Deixa o relógio bater mais algumas vezes, para a pausa (se houve) entrar na medida. */
void encerrarDepois(Timer relogio, CountDownLatch fim) {
var parar = new Timer(300, _ -> { relogio.stop(); fim.countDown(); });
parar.setRepeats(false);
parar.start();
}
List<String> consultaLenta() {
try { Thread.sleep(1500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
return List.of("Lagoinha do Leste", "Costão do Santinho");
}
$ java -Djava.awt.headless=true CongelaOuNao.java
consulta na EDT: maior pausa da interface = 1501 ms
consulta em SwingWorker: maior pausa da interface = 51 ms
Com a consulta na EDT, a interface ficou 1,5 segundo congelada. É aquele momento em que a janela fica cinza, o sistema diz "não está respondendo", e o usuário clica mais três vezes, empilhando eventos. Com o SwingWorker, o relógio nunca atrasou mais que o próprio intervalo. Repare que o programa roda com java.awt.headless=true, sem janela nenhuma: o Timer e o SwingWorker funcionam mesmo sem monitor, o que torna esse tipo de medição possível num servidor de integração contínua.
O SwingWorker é o equivalente Swing do padrão que você conhece de "Encadeando o que Ainda Não Aconteceu — CompletableFuture". O doInBackground() roda numa thread de trabalho e pode demorar o quanto quiser, sem tocar em componentes. O done() roda de volta na EDT e é o lugar de atualizar a tela com o resultado. A primeira regra, "só a EDT mexe nos componentes", também explica o SwingUtilities.invokeLater, que aparece no main de todo programa Swing bem escrito: até a criação da janela deve acontecer na EDT.
Uma janela de catálogo em Swing
A janela abaixo mostra as trilhas numa tabela, filtra pelo nome enquanto se digita, ordena ao clicar no cabeçalho e recarrega os dados em segundo plano com um indicador de progresso. Por enquanto, os dados são fixos. Na aula que liga o desktop à API, eles virão por HTTP.
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableRowSorter;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.List;
import java.util.regex.Pattern;
/** Os dados que a janela mostra. Na aula do app que consome a API, virão de HTTP. */
record Trilha(String nome, String regiao, double distanciaKm, String dificuldade) {}
/** O "Model" do JTable: a tabela pergunta, o modelo responde. Ela nunca guarda os dados. */
final class ModeloDeTrilhas extends AbstractTableModel {
private static final String[] COLUNAS = {"Nome", "Região", "Distância (km)", "Dificuldade"};
private List<Trilha> trilhas = List.of();
void trocar(List<Trilha> novas) {
trilhas = List.copyOf(novas);
fireTableDataChanged(); // avisa a JTable: "redesenhe-se"
}
@Override public int getRowCount() { return trilhas.size(); }
@Override public int getColumnCount() { return COLUNAS.length; }
@Override public String getColumnName(int c) { return COLUNAS[c]; }
@Override public Class<?> getColumnClass(int c) {
return c == 2 ? Double.class : String.class; // Double: alinha à direita e ordena como número
}
@Override public Object getValueAt(int linha, int coluna) {
Trilha t = trilhas.get(linha);
return switch (coluna) {
case 0 -> t.nome();
case 1 -> t.regiao();
case 2 -> t.distanciaKm();
default -> t.dificuldade();
};
}
}
final class JanelaDoCatalogo extends JFrame {
private final ModeloDeTrilhas modelo = new ModeloDeTrilhas();
private final TableRowSorter<ModeloDeTrilhas> ordenador = new TableRowSorter<>(modelo);
private final JTextField busca = new JTextField(20);
private final JButton recarregar = new JButton("Recarregar");
private final JLabel status = new JLabel(" ");
private final JProgressBar progresso = new JProgressBar();
JanelaDoCatalogo() {
super("Trilhas da Ilha — Catálogo");
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
var tabela = new JTable(modelo);
tabela.setRowSorter(ordenador); // clique no cabeçalho ordena
tabela.setAutoCreateRowSorter(false);
tabela.setFillsViewportHeight(true);
var topo = new JPanel(new FlowLayout(FlowLayout.LEFT));
topo.add(new JLabel("Buscar:"));
topo.add(busca);
topo.add(recarregar);
progresso.setIndeterminate(true);
progresso.setVisible(false);
var rodape = new JPanel(new BorderLayout(8, 0));
rodape.setBorder(BorderFactory.createEmptyBorder(4, 8, 4, 8));
rodape.add(status, BorderLayout.CENTER);
rodape.add(progresso, BorderLayout.EAST);
add(topo, BorderLayout.NORTH);
add(new JScrollPane(tabela), BorderLayout.CENTER);
add(rodape, BorderLayout.SOUTH);
busca.getDocument().addDocumentListener(new DocumentListener() {
@Override public void insertUpdate(DocumentEvent e) { filtrar(); }
@Override public void removeUpdate(DocumentEvent e) { filtrar(); }
@Override public void changedUpdate(DocumentEvent e) { filtrar(); }
});
recarregar.addActionListener(_ -> carregar());
setSize(640, 300);
setLocationRelativeTo(null);
}
/** Filtra pelo nome, sem diferenciar maiúsculas; Pattern.quote: o texto é texto, não regex. */
private void filtrar() {
String texto = busca.getText().strip();
ordenador.setRowFilter(texto.isEmpty() ? null
: RowFilter.regexFilter("(?iu)" + Pattern.quote(texto), 0));
status.setText(tabelaVisivel() + " de " + modelo.getRowCount() + " trilhas");
}
private int tabelaVisivel() { return ordenador.getViewRowCount(); }
/** A consulta roda FORA da EDT; o resultado volta para ela no done(). */
void carregar() {
recarregar.setEnabled(false);
progresso.setVisible(true);
status.setText("Carregando…");
new SwingWorker<List<Trilha>, Void>() {
@Override protected List<Trilha> doInBackground() throws Exception {
Thread.sleep(800); // finge a latência da rede
return List.of(
new Trilha("Lagoinha do Leste", "Sul", 4.5, "Moderada"),
new Trilha("Costão do Santinho", "Norte", 3.2, "Fácil"),
new Trilha("Morro da Coroa", "Leste", 6.8, "Difícil"),
new Trilha("Praia do Saquinho", "Sul", 2.1, "Fácil"),
new Trilha("lagoa Pequena", "Leste", 1.2, "Fácil"));
}
@Override protected void done() {
try {
modelo.trocar(get()); // get() aqui não bloqueia: já terminou
filtrar();
} catch (Exception e) {
status.setText("Falhou: " + e.getCause().getMessage());
} finally {
progresso.setVisible(false);
recarregar.setEnabled(true);
}
}
}.execute();
}
}
void main(String[] args) throws Exception {
SwingUtilities.invokeLater(() -> { // TODA a interface nasce na EDT
var janela = new JanelaDoCatalogo();
janela.setVisible(true);
janela.carregar();
});
if (args.length > 0) { // só para o teste: digita, fotografa e sai
Thread.sleep(2000);
SwingUtilities.invokeAndWait(() -> {
var j = (JanelaDoCatalogo) Frame.getFrames()[0];
((JTextField) ((JPanel) j.getContentPane().getComponent(0)).getComponent(1)).setText("lago");
});
Thread.sleep(500);
SwingUtilities.invokeAndWait(() -> {
try {
var j = Frame.getFrames()[0];
var img = new BufferedImage(j.getWidth(), j.getHeight(), BufferedImage.TYPE_INT_RGB);
j.paint(img.getGraphics());
ImageIO.write(img, "png", new File(args[0]));
IO.println("status: " + ((JLabel) ((JPanel) ((JFrame) j).getContentPane().getComponent(2)).getComponent(0)).getText());
} catch (Exception e) { throw new RuntimeException(e); }
});
System.exit(0);
}
}
Rode com java CatalogoSwing.java para usar a janela normalmente. O trecho depois do if existe só para testes automatizados: com um caminho de arquivo como argumento, o programa digita "lago" na busca, desenha a janela numa imagem e sai. No teste, feito numa tela virtual, a imagem mostrou a tabela com "Lagoinha do Leste" e "lagoa Pequena", a distância alinhada à direita, e o rodapé:
status: 2 de 5 trilhas
As peças que merecem estudo:
- O
TableModelé o dono dos dados. AJTablechamagetRowCount()egetValueAt()apenas para as células visíveis, e por isso uma tabela com cem mil linhas continua rápida. Quando os dados mudam, o modelo avisa comfireTableDataChanged(), e a tabela se redesenha. OgetColumnClassdevolvendoDouble.classpara a distância muda duas coisas: o renderizador padrão alinha números à direita, e a ordenação passa a ser numérica, em que 12,0 vem depois de 4,5, e não antes, como seria comparando texto. - Filtro e ordenação são uma camada separada. O
TableRowSorterfica entre o modelo e a tabela, traduzindo índices da vista para o modelo. O modelo continua com as cinco trilhas, e só a vista mostra duas. O filtro usaPattern.quoteporque o texto digitado é texto, e não uma expressão regular: sem isso, digitar(lançaria uma exceção, e digitar.*mostraria tudo. É a mesma lição da injeção de SQL, agora em outro lugar. - Gerenciadores de layout, não coordenadas. Nenhum componente tem posição em pixels. O
BorderLayoutdivide a janela em norte, sul, centro, leste e oeste, com o centro ocupando o espaço que sobra. OFlowLayoutenfileira componentes na horizontal. Por isso a janela se ajusta sozinha a outro tamanho, a outra fonte ou a outro idioma com palavras mais longas. Posições fixas em pixels quebram na primeira tela de alta densidade. - O
DocumentListenerescuta o conteúdo do campo, e não as teclas. Colar um texto, apagar com o mouse ou alterar pelo código também disparam o filtro. EscutarKeyEventpara isso é um erro comum, que ignora o colar.
Para dar ao programa a aparência do sistema operacional, basta uma linha antes de criar a janela: UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()). Para um visual moderno, a biblioteca FlatLaf faz o mesmo com FlatLightLaf.setup(). E para entregar o programa a um usuário que nem sabe o que é Java, o jpackage de "Do JAR ao Executável — jlink, jpackage e GraalVM Native Image" gera um instalador .msi, .dmg ou .deb, com uma JVM enxuta embutida.
O Swing fez tudo o que precisávamos, mas repare no volume de código cerimonial. São três métodos do DocumentListener para dizer uma coisa só ("quando o texto mudar, filtre"), um modelo de tabela escrito à mão, a atualização manual do rodapé em dois lugares e a troca manual de estado do botão e da barra de progresso. É esse volume que o JavaFX ataca, com o layout num arquivo FXML, a aparência em CSS e bindings: expressões que a interface mantém verdadeiras por conta própria.
Fontes e leituras recomendadas
- The Java Tutorials — Concurrency in Swing: a EDT, o
invokeLatere oSwingWorker, direto de quem os projetou. - The Java Tutorials — How to Use Tables:
TableModel, renderizadores, ordenação e filtros. - A Swing Architecture Overview (Oracle): o artigo clássico sobre componentes leves e a arquitetura modelo-visão do Swing.
- JavaFX — Wikipedia: a linha do tempo, da JavaFX Script ao OpenJFX.
- FlatLaf: o look and feel moderno para Swing usado por várias IDEs.
- OpenJFX: o projeto JavaFX, com documentação e lançamentos.
Exercícios
Exercício 1
Um colega carrega as trilhas assim, no clique do botão: modelo.trocar(fonte.buscarTodas());, e a busca demora 3 segundos. Descreva o que o usuário vê e por que, e reescreva o trecho corretamente.
Ver resposta
✓ Resposta: O clique é tratado na EDT, e a chamada bloqueia essa thread por 3 segundos. Durante esse tempo, nada é redesenhado: o botão fica "afundado", a janela não responde a cliques nem a redimensionamentos, e o sistema pode mostrar "não está respondendo". Os cliques feitos nesse intervalo ficam enfileirados e são processados todos de uma vez no fim. A correção usa um SwingWorker: doInBackground() chama fonte.buscarTodas(), e done() chama modelo.trocar(get()) com o tratamento de exceção, exatamente como o método carregar() da aula.
Exercício 2
Por que a coluna de distância usa getColumnClass devolvendo Double.class? Descreva dois efeitos visíveis de removê-lo.
Ver resposta
✓ Resposta: Sem ele, a JTable trata todas as colunas como Object e usa o renderizador e o comparador genéricos. O primeiro efeito é que os números passam a ser alinhados à esquerda, como texto. O segundo é que a ordenação passa a comparar pela representação em texto (toString()), e distâncias como 12.0 e 4.5 ficam em ordem errada, com "12.0" antes de "4.5", porque o caractere "1" vem antes do "4". Declarar o tipo da coluna é informação que a tabela usa para escolher o renderizador e o comparador certos.
Exercício 3
Remova o Pattern.quote do método filtrar e digite ( na busca. O que acontece? E se digitar .?
Ver resposta
✓ Resposta: Com (, RowFilter.regexFilter tenta compilar a expressão (?iu)(, que é inválida, e lança PatternSyntaxException dentro do tratador do DocumentListener, na EDT. O filtro não é aplicado e a exceção aparece no console. Com ., a expressão é válida e significa "qualquer caractere", e todas as trilhas aparecem, embora nenhuma tenha um ponto no nome. Nos dois casos, o texto do usuário foi interpretado como código (uma expressão regular), quando deveria ser dado. O Pattern.quote o transforma num literal.
Exercício 4
Acrescente ao catálogo em Swing um JComboBox de região ("Todas", "Norte", "Sul", "Leste") que se combina com o filtro de nome.
Ver resposta
✓ Resposta: Crie var regiao = new JComboBox<>(new String[]{"Todas", "Norte", "Sul", "Leste"});, adicione-o ao painel do topo e registre regiao.addActionListener(_ -> filtrar());. No filtrar(), monte uma lista de filtros: var filtros = new ArrayList<RowFilter<ModeloDeTrilhas, Integer>>();, acrescente o filtro de nome quando houver texto e, quando a região não for "Todas", RowFilter.regexFilter("^" + Pattern.quote(sel) + "$", 1) (coluna 1, correspondência exata). Por fim, ordenador.setRowFilter(filtros.isEmpty() ? null : RowFilter.andFilter(filtros));. O andFilter combina os critérios sem mexer no modelo.
Exercício 5
Explique, com as suas palavras, por que as bibliotecas gráficas usam uma única thread em vez de travas em cada componente, e qual é o preço dessa escolha para quem programa.
Ver resposta
✓ Resposta: Eventos do sistema (cliques, teclas, pedidos de repintura) atravessam os componentes de fora para dentro, enquanto as mudanças feitas pelo programa atravessam de dentro para fora. Com uma trava por componente, esses dois fluxos adquirem travas em ordens opostas, e é a receita clássica de deadlock, que as primeiras bibliotecas multithread não conseguiram evitar na prática. Uma única thread processando uma fila de eventos elimina as travas e os deadlocks. O preço fica com o programador: nenhuma operação demorada pode rodar nessa thread (ou a tela congela), e nenhuma outra thread pode tocar nos componentes (ou o estado se corrompe). Daí as ferramentas como invokeLater, SwingWorker e, no JavaFX, Platform.runLater e Task.