Жизнен цикъл на нишка в Java

1. Въведение

В тази статия ще обсъдим подробно една основна концепция в Java - жизнения цикъл на нишката.

Ще използваме бърза илюстрирана диаграма и, разбира се, практически кодови фрагменти, за да разберем по-добре тези състояния по време на изпълнението на нишката.

За да започнете да разбирате нишките в Java, тази статия за създаване на нишка е добро място за начало.

2. Многопоточност в Java

В езика Java многопоточността се задвижва от основната концепция за нишка . По време на жизнения си цикъл нишките преминават през различни състояния:

3. Жизнен цикъл на нишка в Java

Класът java.lang.Thread съдържа статично изброяване на състоянието - което определя потенциалните му състояния. През даден момент от време нишката може да бъде само в едно от следните състояния:

  1. НОВО - новосъздадена нишка, която все още не е започнала изпълнението
  2. RUNNABLE - или работещ, или готов за изпълнение, но чака разпределение на ресурсите
  3. БЛОКИРАН - изчакване за придобиване на заключване на монитора за въвеждане или повторно въвеждане на синхронизиран блок / метод
  4. ИЗЧАКВАНЕ - изчаква се някаква друга нишка да извърши определено действие без ограничение във времето
  5. TIMED_WAITING - изчакване някоя друга нишка да извърши конкретно действие за определен период
  6. ПРЕКРАТЕН - е завършил изпълнението си

Всички тези състояния са обхванати в диаграмата по-горе; нека сега обсъдим всеки от тях подробно.

3.1. Ново

A NEW Тема (или Born Тема ) е нишката, която е била създадена, но все още не е започнало. Той остава в това състояние, докато не го стартираме чрез метода start () .

Следният кодов фрагмент показва новосъздадена нишка, която е в НОВО състояние:

Runnable runnable = new NewState(); Thread t = new Thread(runnable); Log.info(t.getState());

Тъй като не сме стартирали споменатата нишка, методът t.getState () отпечатва:

NEW

3.2. Изпълним

Когато създадем нова нишка и извикаме метода start () по нея, тя се премества от NEW в състояние RUNNABLE . Нишките в това състояние или се изпълняват, или са готови за изпълнение, но те чакат разпределението на ресурсите от системата.

В среда с много нишки, Thread-Scheduler (който е част от JVM) разпределя фиксиран период от време за всяка нишка. Така че тя работи за определен период от време, след което се отказва от контролата на други RUNNABLE нишки.

Например, нека добавим метода t.start () към предишния ни код и се опитаме да получим текущото му състояние:

Runnable runnable = new NewState(); Thread t = new Thread(runnable); t.start(); Log.info(t.getState());

Този код най-вероятно ще върне изхода като:

RUNNABLE

Имайте предвид, че в този пример не винаги е гарантирано, че докато контролът ни достигне t.getState () , той все още ще бъде в състояние RUNNABLE .

Може да се случи така, че той да е бил незабавно планиран от Thread-Scheduler и да завърши изпълнението. В такива случаи може да получим различен изход.

3.3. Блокиран

Нишката е в БЛОКИРАНО състояние, когато в момента не отговаря на условията за изпълнение. Той влиза в това състояние, когато чака заключване на монитора и се опитва да получи достъп до част от код, която е заключена от друга нишка.

Нека се опитаме да възпроизведем това състояние:

public class BlockedState { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new DemoThreadB()); Thread t2 = new Thread(new DemoThreadB()); t1.start(); t2.start(); Thread.sleep(1000); Log.info(t2.getState()); System.exit(0); } } class DemoThreadB implements Runnable { @Override public void run() { commonResource(); } public static synchronized void commonResource() { while(true) { // Infinite loop to mimic heavy processing // 't1' won't leave this method // when 't2' try to enter this } } }

В този код:

  1. Създадохме две различни нишки - t1 и t2
  2. t1 стартира и въвежда синхронизирания метод commonResource () ; това означава, че само една нишка може да има достъп до нея; всички други последващи нишки, които се опитват да получат достъп до този метод, ще бъдат блокирани от по-нататъшното изпълнение, докато текущата завърши обработката
  3. Когато t1 влезе в този метод, той се поддържа в безкраен цикъл while; това е само за имитация на тежка обработка, така че всички останали нишки да не могат да влязат в този метод
  4. Сега, когато стартираме t2 , той се опитва да въведе метода commonResource () , който вече е достъпен от t1, като по този начин t2 ще бъде запазен в състояние BLOCKED

В това състояние извикваме t2.getState () и получаваме изхода като:

BLOCKED

3.4. Очакване

Нишката е в състояние WAITING, когато чака друга нишка да извърши определено действие. Според JavaDocs, всяка нишка може да влезе в това състояние, като извика някой от следните три метода:

  1. object.wait ()
  2. thread.join () или
  3. LockSupport.park ()

Имайте предвид, че в wait () и join () - ние не дефинираме период на изчакване, тъй като този сценарий е описан в следващия раздел.

Имаме отделен урок, който обсъжда подробно използването на wait () , notify () и notifyAll () .

Засега нека се опитаме да възпроизведем това състояние:

public class WaitingState implements Runnable { public static Thread t1; public static void main(String[] args) { t1 = new Thread(new WaitingState()); t1.start(); } public void run() { Thread t2 = new Thread(new DemoThreadWS()); t2.start(); try { t2.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Log.error("Thread interrupted", e); } } } class DemoThreadWS implements Runnable { public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Log.error("Thread interrupted", e); } Log.info(WaitingState.t1.getState()); } }

Нека да обсъдим какво правим тук:

  1. Създадохме и стартирахме t1
  2. t1 създава t2 и го стартира
  3. Докато обработката на t2 продължава, ние наричаме t2.join () , това поставя t1 в състояние WAITING, докато t2 завърши изпълнението
  4. Тъй като t1 чака t2 да завърши, ние извикваме t1.getState () от t2

Резултатът тук е, както бихте очаквали:

WAITING

3.5. Временно изчакване

Нишката е в състояние TIMED_WAITING, когато чака друга нишка да извърши определено действие в рамките на определен период от време.

Според JavaDocs има пет начина за поставяне на нишка в състояние TIMED_WAITING :

  1. thread.sleep (дълги мили)
  2. изчакайте (int timeout) или изчакайте (int timeout, int nanos)
  3. thread.join (дълго милисекунди )
  4. LockSupport.parkNanos
  5. LockSupport.parkUntil

За да прочетете повече за разликите между wait () и sleep () в Java, разгледайте тази специална статия тук.

Засега нека се опитаме бързо да възпроизведем това състояние:

public class TimedWaitingState { public static void main(String[] args) throws InterruptedException { DemoThread obj1 = new DemoThread(); Thread t1 = new Thread(obj1); t1.start(); // The following sleep will give enough time for ThreadScheduler // to start processing of thread t1 Thread.sleep(1000); Log.info(t1.getState()); } } class DemoThread implements Runnable { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Log.error("Thread interrupted", e); } } }

Тук създадохме и стартирахме нишка t1, която влиза в състояние на заспиване с период на изчакване от 5 секунди; изходът ще бъде:

TIMED_WAITING

3.6. Прекратено

This is the state of a dead thread. It's in the TERMINATED state when it has either finished execution or was terminated abnormally.

We have a dedicated article that discusses different ways of stopping the thread.

Let's try to achieve this state in the following example:

public class TerminatedState implements Runnable { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new TerminatedState()); t1.start(); // The following sleep method will give enough time for // thread t1 to complete Thread.sleep(1000); Log.info(t1.getState()); } @Override public void run() { // No processing in this block } }

Here, while we've started thread t1, the very next statement Thread.sleep(1000) gives enough time for t1 to complete and so this program gives us the output as:

TERMINATED

In addition to the thread state, we can check the isAlive() method to determine if the thread is alive or not. For instance, if we call the isAlive() method on this thread:

Assert.assertFalse(t1.isAlive());

Връща false. Казано по-просто, нишката е жива, ако и само ако е стартирана и все още не е умряла.

4. Заключение

В този урок научихме за жизнения цикъл на нишка в Java. Разгледахме всичките шест състояния, дефинирани от Thread.State enum, и ги възпроизведохме с бързи примери.

Въпреки че кодовите фрагменти ще дават един и същ изход в почти всяка машина, в някои изключителни случаи можем да получим различни изходи, тъй като точното поведение на Thread Scheduler не може да бъде определено.

И както винаги, използваните тук кодови фрагменти са налични в GitHub.