Источник: Nuances of Programming
«Гораздо проще уже спроектировать класс потокобезопасным, чем модернизировать его позже».
― Брайан Гетц.
Потоки Java играют важную роль в параллельном программировании. Поток в любой момент времени находится только в одном из показанных на схеме ниже состояний:
Прежде чем переходить к рассмотрению состояний потоков, неплохо было бы освежить знания об основах параллельного программирования.
1. New
Когда создается новый поток, он находится в состоянии New , причем запускаться поток еще не начал. Не начал запускаться его код, ему еще предстоит выполниться.
Пример: новый поток создается, но не запускается, поэтому остается вот таким:
Thread newThread = new newThreadClass();
Объект Thread пуст, и ресурсы для потока недоступны. Если пользователь вызовет какой-то другой метод, кроме start() , произойдет ошибка IllegalThreadStateExecption .
2. Runnable
Поток, готовый к запуску, переходит в состояние runnable .
Thread newThread = new newThreadClass();
newThread.start();
В этом состоянии поток либо запускается, либо готов в любой момент запуститься. Дальше свою работу выполняет планировщик потоков, предоставляющий время для выполнения потока. В большинстве операционных систем он каждому потоку выделяет небольшое количество процессорного времени. Когда это происходит, все такие потоки, которые готовы к запуску, ждут центральный процессор, а выполняемый в этот момент времени поток находится в состоянии runnable .
3. Blocked
При попытке выполнить задачу, которая не может быть завершена в данный момент времени, поток из состояния runnable переходит в состояние blocked . И ждет, пока задача не будет завершена.
Например, когда поток ожидает завершения операций ввода-вывода, он находится в состоянии blocked . Поток в этом состоянии не может дальше продолжать выполнение до тех пор, пока не перейдет в состояние runnable . Планировщик потоков повторно активирует blocked/waiting (блокированный/ожидающий) поток и планирует его выполнение. Любой поток, находясь в одном из этих состояний, не потребляет процессорное время.
4. Waiting
Когда поток находится в состоянии waiting , он ждет другой поток, связанный условием. Когда это условие выполняется, планировщик получает уведомление и вызываются методы notify () или notifyAll() . В этом случае ожидающий поток переходит в состояние runnable .
Если выполняемый в это время поток переходит в состояние blocked/waiting , планировщик дает добро на выполнение ожидающего потока, перешедшего в состояние runnable . Именно планировщик потоков определяет, какой поток должен выполняться.
5. Time waiting
Поток находится в состоянии runnable . Теперь он вызывает метод sleep(t) , wait(t) или join(t) с неким промежутком времени в качестве параметра и переходит в состояние time waiting . Поток остается в этом состоянии до тех пор, пока время ожидания не выйдет или пока не будет получено уведомление. Например, когда поток вызывает sleep или условное ожидание, он переходит в состояние timed waiting (ожидание с ограничением по времени). Как только время выйдет, поток вернется в состояние runnable .
6. Terminate
Поток завершается по любой из следующих причин:
· Поток завершается в обычном режиме, когда код потока полностью выполнен программой.
· При выполнении потока произошло какое-то нештатное событие, сопровождаемое появлением ошибки, например ошибки сегментации или необработанного исключения.
Планирование потоков
Говоря о потоках и состояниях потоков в Java, не стоит забывать о планировании потоков.
Планирование потоков применяется для определения приоритета потоков. При запуске потока ему достается определенный приоритет: как максимум MAX_PRIORITY= 10 и как минимум MIN_PRIORITY = 1 .
Обычный приоритет будет равен пяти (NORMAL_PRIORITY = 5 ).
Согласно правилу планирования потоков, добро на выполнение всегда дается потоку с более высоким приоритетом, а поток с низким уровнем приоритета переходит в состояние waiting .
Если потоки имеют равный приоритет, то они будут выполняться согласно методу Round-Robin, т. е. перебором по круговому циклу.
В этом сценарии racer[0] и racer[1] имеют значение приоритета 7 , а у racer[3] оно равно 3 .
То есть racer[0] и racer[1] будут выполняться как первый поток, так как у них самый высокий приоритет среди потоков в состоянии RUNNABLE .
Что касается метода Round-Robin: если racer[0] будет выбран первым на выполнение, то другие будут ожидать его завершения. После чего начнет выполняться racer[1] . Они будут выполняться до тех пор, пока их процесс не будет завершен. И только затем запустится поток racer[3] , имеющий самый низкий приоритет. Он будет выполняться без каких-либо помех до завершения своего процесса.
Надеюсь, вы получили четкое представление о состояниях и планировании потоков в Java. Остается только немного попрактиковаться.
Читайте также:
Перевод статьи Ravidu Perera : States of Thread in Java