Un thread indique qu'il est encore en vie (true) ou mort (false), par l'intermédiaire de la méthode d'instance isAlive().

if(thread.isAlive())
    System.out.println("Le thread est toujours actif !");
else
    System.out.println("Le thread est terminé !");

Bien que l'objet Thread soit encore accessible, il n'est plus possible, si le résultat de la méthode isAlive() est false, de relancer l'exécution du thread mort. Puisqu'inutilisable, il peut être utile de libérer ses ressources en le déréférençant par l'intermédiaire d'une valeur null.

if(thread.isAlive())
    thread = null;

Dans le cas des threads démons, la vérification de l'existence de ce type d'unités d'exécution peut se révéler très utile pour maintenir actif le thread principal.

public class RelationThreads extends Thread {
    private long debut;
    private int pause;

    public RelationThreads(int pause) {
        this.debut = System.currentTimeMillis();
        this.pause = pause;
    }
    public void run() {
        for (int i = 0; i < 10; i++) {
            afficher(i + " - Exécution du Thread", pause);
        }
    }
    public void afficher(String msg, int pause) {
        long temps = System.currentTimeMillis();
        Thread t = Thread.currentThread();
        System.out.println(msg + " : " + t.getName() 
                        + "(" + (temps - debut) + " ms)");
        try {
            Thread.sleep(pause);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        System.out.println("Début du thread principal");
        RelationThreads t1 = new RelationThreads(100);
        t1.setName("Thread utilisateur 1");
        t1.setDaemon(true);
        t1.start();

        RelationThreads t2 = new RelationThreads(200);
        t2.setName("Thread utilisateur 2");
        t2.setDaemon(true);
        t2.start();

        boolean fin1 = false;
        boolean fin2 = false;
        while (true) {
            if (!fin1 && !t1.isAlive()) {
                System.out.println("Fin de " + t1.getName());
                t1 = null;
                fin1 = true;
            }
            if (!fin2 && !t2.isAlive()) {
                System.out.println("Fin de " + t2.getName());
                t2 = null;
                fin2 = true;
            }
            if(fin1 && fin2) break;
            try {
                Thread.sleep(100);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Fin du thread principal");
    }
}
/* Affiche :
Début du thread principal
0 - Exécution du Thread : Thread utilisateur 2(0 ms)
0 - Exécution du Thread : Thread utilisateur 1(0 ms)
1 - Exécution du Thread : Thread utilisateur 1(94 ms)
1 - Exécution du Thread : Thread utilisateur 2(204 ms)
2 - Exécution du Thread : Thread utilisateur 1(204 ms)
3 - Exécution du Thread : Thread utilisateur 1(297 ms)
2 - Exécution du Thread : Thread utilisateur 2(407 ms)
4 - Exécution du Thread : Thread utilisateur 1(407 ms)
5 - Exécution du Thread : Thread utilisateur 1(500 ms)
3 - Exécution du Thread : Thread utilisateur 2(594 ms)
6 - Exécution du Thread : Thread utilisateur 1(610 ms)
7 - Exécution du Thread : Thread utilisateur 1(704 ms)
4 - Exécution du Thread : Thread utilisateur 2(797 ms)
8 - Exécution du Thread : Thread utilisateur 1(813 ms)
9 - Exécution du Thread : Thread utilisateur 1(907 ms)
5 - Exécution du Thread : Thread utilisateur 2(1000 ms)
Fin de Thread utilisateur 1
6 - Exécution du Thread : Thread utilisateur 2(1204 ms)
7 - Exécution du Thread : Thread utilisateur 2(1407 ms)
8 - Exécution du Thread : Thread utilisateur 2(1594 ms)
9 - Exécution du Thread : Thread utilisateur 2(1797 ms)
Fin de Thread utilisateur 2
Fin du thread principal
*/