Stany wątków w Javie

Stany wątków są częstym pytaniem na rozmowie kwalifikacyjnej z działu wielowątkowości. W Javie wątki mogą znajdować się w sześciu stanach. Każda zmiana stanu poprzedzona jest jakimś zdarzeniem. Co to za zdarzenia? Zapraszam do wpisu, aby się o tym dowiedzieć!

Stan

Aby pobrać stan wątku wykorzystujemy wbudowaną metodę getState(), która znajduje się w klasie Thread:

final Thread newThread = new Thread();
newThread.getState();

Jak napisałem we wstępie, wątek może znajdować się w jednym z sześciu stanów:

NEW

Wątek w stanie NEW znajduje się zaraz po utworzeniu. Posiada on ten stan dopóki nie uruchomimy metody start():

@Test
void shouldReturnNewState() {
    // Given
    final Thread newThread = new Thread();
    // Then
    assertThat(newThread.getState()).isEqualTo(Thread.State.NEW);
}

RUNNABLE

Po wywołaniu metody start() na wątku, zmienia on swój stan z NEW na RUNNABLE:

@Test
void shouldReturnRunnableState() throws InterruptedException {
    // Given
    final Thread runnableThread = new Thread(() -> {
        assertThat(Thread.currentThread().getState()).isEqualTo(Thread.State.RUNNABLE);
    });
    // When
    runnableThread.start();
    runnableThread.join();
}

BLOCKED

Wątek w stanie BLOCKED może znaleźć się wtedy, kiedy dostęp do sekcji krytycznej jest aktualnie zajęty (znajduje się blokada na monitorze). Innymi słowy, kiedy wątek będzie próbował się dostać do synchronizowanej metody, a wewnątrz znajduje się już inny wątek, to stan zmieniany jest na BLOCKED:

@Test
void shouldReturnBlockedState() throws InterruptedException {
    // Given
    final Thread synchronizedThread = new Thread(this::synchronizedMethod);
    final Thread blockedThread = new Thread(this::synchronizedMethod);

    // When
    synchronizedThread.start();
    blockedThread.start();

    // Then
    Thread.sleep(10);
    assertThat(blockedThread.getState()).isEqualTo(Thread.State.BLOCKED);
}

WAITING

Zmiana wątku na stan WAITING odbywa się wtedy, gdy wątek czeka na inne wątki. Nie jest to czekanie jak w sekcji krytycznej, tylko czekanie wywołane przykładowo metodą wait() czy join():

@Test
void shouldReturnWaitingState() throws InterruptedException {
    // Given
    final Thread waitingThread = new Thread(this::synchronizedMethod);
    final Thread wrapperThread = new Thread(() -> {
        waitingThread.start();
        try {
            Thread.currentThread().join();
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
    });

    // When
    wrapperThread.start();

    // Then
    Thread.sleep(10);
    assertThat(wrapperThread.getState()).isEqualTo(Thread.State.WAITING);
}

TIMED_WAITING

Każdemu z nas przydarzyło się przenieść wątek w stan uśpienia. Posiada on wtedy stan TIMED_WAITING. Ten stan pojawia się również, gdy używamy metod do czekania na inne wątki (jak wait() czy join()), które w parametrach przyjmują wartości czasowe:

@Test
void shouldReturnTimedWaitingState() throws InterruptedException {
    // Given
    final Thread timedWaitingThread = new Thread(() -> {
        try {
            Thread.sleep(1_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });

    // When
    timedWaitingThread.start();

    // Then
    Thread.sleep(10);
    assertThat(timedWaitingThread.getState()).isEqualTo(Thread.State.TIMED_WAITING);
}

TERMINATED

Po zakończeniu swojej pracy (czyli po zakończeniu metody run()) wątek przechodzi w stan TERMINATED:

@Test
void shouldReturnTerminatedState() throws InterruptedException {
    // Given
    final Thread terminatedThread = new Thread(()->{});

    // When
    terminatedThread.start();
    terminatedThread.join();

    // Then
    assertThat(terminatedThread.getState()).isEqualTo(Thread.State.TERMINATED);
}

Github

Całość jak zawsze na Githubie.