Практически любое техническое интервью по Java содержит несколько вопросов, охватывающих темы параллелизма Java. Обычный сценарий для звука начального и среднего уровня звучит так:
Интервьюер: Можете ли вы перечислить состояния жизненного цикла потока Java?
Кандидат:  Да, конечно! Итак, мы имеем: NEW , RUNNABLE , BLOCKED , WAITING , TIMED_WAITING , и TERMINATED .
Опрашивающий:  Хорошо! Теперь, вы можете написать фрагмент кода, который показывает BLOCKED состояние?
Что ж, давайте посмотрим, как мы можем написать фрагменты кодов для каждого состояния.
Вам также может понравиться: Как читать дамп темы
Состояния потока Java выражаются посредством Thread.State перечисления. Возможные состояния потока Java показаны на следующей диаграмме:
Существуют следующие состояния жизненного цикла:
- NEWСостояние.
- RUNNABLEСостояние.
- BLOCKEDСостояние.
- WAITINGСостояние.
- TIMED_WAITINGСостояние.
- TERMINATEDСостояние.
Давайте узнаем обо всех различных состояниях в следующих разделах.
НОВОЕ Государство
Поток Java находится в NEW состоянии, если он создан, но не запущен (конструктор потока создает потоки в NEW состоянии). Это его состояние, пока start() метод не будет вызван. Код в комплекте с этой статьей содержит несколько фрагментов коды , которые показывают это состояние с помощью различных методов строительства, в том числе лямбды. Для краткости ниже приводится лишь одна из этих конструкций:
Джава
xxxxxxxxxx
1
public class NewThread {
2
  public void newThread() {
3
      Thread t = new Thread(() -> {});
4
      System.out.println("NewThread: " + t.getState()); // NEW
5
    }
6
}
7
           
8
//usage
9
NewThread nt = new NewThread();
10
nt.newThread();
БЕЗОПАСНОЕ Государство
Переход от NEW к RUNNABLE получен, вызывая start()  метод. В этом состоянии поток может быть запущен или готов к работе. Когда он готов к запуску, поток ожидает, пока планировщик потока JVM не выделит необходимые ресурсы и время для его запуска.
Как только процессор станет доступен, поток-планировщик запустит поток. Следующий фрагмент кода должен напечатать RUNNABLE , так как мы печатаем состояние потока после вызова  start() . Но из-за внутренних механизмов планировщика потоков это не гарантируется (интервьюеры любят такие подсказки):
Джава
xxxxxxxxxx
1
public class RunnableThread {
2
  public void runnableThread() {
3
    Thread t = new Thread(() -> {});
4
    t.start();
5
    
6
    // RUNNABLE
7
    System.out.println("RunnableThread : " + t.getState());
8
  }
9
}
10
   
11
// usage
12
RunnableThread rt = new RunnableThread();
13
rt.runnableThread();
ЗАБЛОКИРОВАНО состояние
Когда поток пытается выполнить задачи ввода-вывода или синхронизированные блоки, он может войти в это BLOCKED состояние. Например, если поток t1 пытается войти в синхронизированный блок кода, к которому уже обращается другой поток t2 , он t1 остается в BLOCKED состоянии до тех пор, пока не сможет получить блокировку.
Этот сценарий сформирован в следующем фрагменте кода:
1. Создайте две темы: t1 и t2 .
2. Начните с t1 помощью  start() метода:
          1. t1  выполнит run() метод и получит блокировку для синхронизированного метода syncMethod() .
          2.syncMethod()  будет держать t1 внутри навсегда, так как он имеет бесконечный цикл.
3. Через две секунды (произвольное время) начните с t2 помощью start()  метода:
          1. t2  выполнит run() код и окажется в BLOCKED состоянии, так как не может получить  
               замок syncMethod() .
Фрагмент кода выглядит следующим образом:
Джава
xxxxxxxxxx
1
public class BlockedThread {
2
  
3
  public void blockedThread() {
4
5
    Thread t1 = new Thread(new SyncCode());
6
    Thread t2 = new Thread(new SyncCode());
7
    
8
    t1.start();
9
    Thread.sleep(2000);
10
    
11
    t2.start();
12
    Thread.sleep(2000);
13
    
14
    System.out.println("BlockedThread t1: "
15
                       + t1.getState() + "(" + t1.getName() + ")");
16
    System.out.println("BlockedThread t2: "
17
                       + t2.getState() + "(" + t2.getName() + ")");
18
19
    System.exit(0);
20
  }
21
  
22
  private static class SyncCode implements Runnable {
23
    
24
    
25
    public void run() {
26
      System.out.println("Thread " + Thread.currentThread().getName()
27
                       + " is in run() method");
28
      syncMethod();
29
    }
30
    
31
    public static synchronized void syncMethod() {
32
      
33
      System.out.println("Thread " + Thread.currentThread().getName()
34
                     + " is in syncMethod() method");
35
      
36
      while (true) {
37
        // t1 will stay here forever, therefore t2 is blocked
38
      }
39
    }
40
  }
41
}
42
     
43
// usage
44
BlockedThread bt = new BlockedThread();
45
bt.blockedThread();
Вот возможный вывод (названия потоков могут отличаться от приведенных здесь):
Джава
xxxxxxxxxx
1
Thread Thread-0 is in run() method
2
Thread Thread-0 is in syncMethod() method
3
Thread Thread-1 is in run() method
4
BlockedThread t1: RUNNABLE(Thread-0)
5
BlockedThread t2: BLOCKED(Thread-1)
Состояние ожидания
Поток, t1 ожидающий (без периода ожидания) другого потока t2 для завершения, находится в WAITING состоянии. Этот сценарий сформирован в следующем фрагменте кода:
1. Создать тему: t1.
2. Начните с t1 помощью start() метода.
3. В run() методе t1:
            1. Создайте другую тему: t2.
            2. Начните с t2 помощью start() метода.
            3. Пока t2 работает, звоните  t2.join() — так как t2 необходимо присоединиться t1 (или, другими словами,t1
       нужно ждать, t2 чтобы умереть), t1 находится в WAITING состоянии.
4. В run() способе t2 , t2 печатает состояние t1 , которое должно быть WAITING (при печати на t1 состояние, t2 работает, поэтому t1 ждет).
Фрагмент кода выглядит следующим образом:
Джава
xxxxxxxxxx
1
public class WaitingThread {
2
  
3
  public void waitingThread() {
4
    new Thread(() -> {
5
      Thread t1 = Thread.currentThread();
6
      Thread t2 = new Thread(() -> {
7
        
8
        Thread.sleep(2000);
9
        System.out.println("WaitingThread t1: "
10
                           + t1.getState()); // WAITING
11
      });
12
      
13
      t2.start();
14
      
15
      t2.join();
16
    
17
    }).start();
18
  }
19
}
20
    
21
// usage
22
WaitingThread wt = new WaitingThread();
23
wt.waitingThread();
Состояние TIMED_WAITING
Поток, t1ожидающий определенного периода времени для другого потока t2, для завершения находится в TIMED_WAITING состоянии. Этот сценарий сформирован в следующем фрагменте кода:
1. Создать тему: t1.
2. Начните с t1 помощью start() метода.
3. В run() методе t1добавьте время ожидания в две секунды (произвольное время).
4. Во время t1 работы основной поток печатает t1 состояние — состояние должно быть, TIMED_WAITING поскольку оно t1 находится в a sleep() , срок действия которого истечет через две секунды.
Фрагмент кода выглядит следующим образом:
Джава
xxxxxxxxxx
1
public class TimedWaitingThread {
2
  
3
  public void timedWaitingThread() {
4
    Thread t = new Thread(() -> {
5
      Thread.sleep(2000);
6
    });    
7
    
8
    t.start();
9
    
10
    Thread.sleep(500);
11
    
12
    System.out.println("TimedWaitingThread t: "
13
                       + t.getState()); // TIMED_WAITING
14
  }
15
}
16
    
17
// usage
18
TimedWaitingThread twt = new TimedWaitingThread();
19
twt.timedWaitingThread();
ПРЕКРАЩЕННОЕ Государство
Поток, который успешно завершает свою работу или ненормально прерван, находится в TERMINATE состоянии. Это очень просто смоделировать, как в следующем фрагменте кода (основной поток приложения печатает состояние потока,  t — когда это происходит, поток tвыполняет свою работу):
Джава
xxxxxxxxxx
1
public class TerminatedThread {
2
  
3
  public void terminatedThread() {
4
    Thread t = new Thread(() -> {});
5
    t.start();
6
    
7
    Thread.sleep(1000);
8
    
9
    System.out.println("TerminatedThread t: "
10
                       + t.getState()); // TERMINATED
11
  }
12
}
13
    
14
// usage
15
TerminatedThread tt = new TerminatedThread();
16
tt.terminatedThread();
Готово! Обычно последний вопрос интервьюера о теме параллелизма Java будет звучать так: Для написания поточно-ориентированных классов какие методы вы рассматриваете? Ну, возможный ответ упомянет следующие методы:
- Не иметь состояния (классы без экземпляра и staticпеременных).
- У государства , но не разделяю его (например, использование переменных экземпляра через Runnable,ThreadLocalи так далее).
- Есть состояние , но неизменное состояние.
- Используйте передачу сообщений (например, как фреймворк Akka).
- Используйте synchronizedблоки.
- Используйте volatileпеременные.
- Используйте структуры данных из java.util.concurrentпакета.
- Используйте синхронизаторы (например, CountDownLatchиBarrier).
- Используйте замки из java.util.concurrent.locksпакета.
Полный код доступен на GitHub .
Если вам понравилась эта статья, вам понравится моя книга « Проблемы кодирования Java» , которая содержит отдельную главу, включающую 27 задач, посвященных теме параллелизма Java.
Дальнейшее чтение
Эволюция проблемы «производитель-потребитель» в Java