O problema é o seguinte.
O Swing tem uma fila de eventos. Nessa fila ele faz o seguinte:
a) Desenha a tela (processando eventos de repaint);
b) Processa os eventos de clique do mouse, teclado e outros inputs;
c) Dispara os eventos de componente
d) Volta para a).
Seu evento te botão está no caso c). Você faz todas as atualizações, mantendo o passo c bloqueado. Essas atualizações são feitas nas propriedades locais do componente, ou seja, na memória. Ainda não houve reflexo na parte visual. Ao final, quando seu último label foi setado, o swing prossegue, só indo pintar a tela novamente no passo a). Note que não adianta colocar sleeps no seu for, pois isso só deixará a aplicação com a aparência de “congelada”.
Como solucionar esse problema? O passo C deve disparar outra thread, que alterá a label, sem bloquear a execução do swing. É o que a solução do colega faz, usando para isso a thread criada pela classe timer.
Nesse post, e nos dois depois dessa, tem 3 forma de se fazer isso (com threads, java.util.Timer e javax.swing.Timer):
http://www.guj.com.br/posts/list/52964.java#279083
O exemplo funciona, você pode copiar e colar no seu Eclipse ou Netbeans. O código está muito comentado.
É por isso que, quando você está trabalhando com Swing, deve disparar eventos longos em outra thread. Caso contrário seu programa parecerá congelado. Como a aplicação ficaria parada no passo c) sem outra thread, o swing não a repintaria até que o processamento termine.