Класс Thread. Класс Thread Состоятельный thread

В какие состояния может перейти нить, при входе в блок synchronized ?

  • RUNNABLE
  • BLOCKED

В RUNNABLE, если блок кода, помеченный synchronized , не занят другой нитью. Иначе наша нить получит состояние BLOCKED и будет ждать освобождения объекта-мютекса.

  • В какое состояние перейдет нить, при вызове метода wait() ?

    Вызов этого метода переводит нить в состояние WAITING.
    Метод wait() можно вызвать только внутри блока synchronized у объекта-мютекса, который был «залочен (заблокирован)» текущей нитью, в противном случае метод выкинет исключение IllegalMonitorStateException .

    Object monitor = getMonitor () ; synchronized (monitor) { … monitor. wait () ; … }
    При вызове метода wait() , текущая нить снимает блокировку с объекта monitor , и переходит в состояние WAITING, ожидая вызова метода monitor.notify() или monitor.notifyAll() другой нитью. Как только это произойдет, нить проснется и если монитор не был занят, то захватит его и продолжит работу.

  • В какое состояние перейдет нить, при вызове метода wait(500) ?

    Вызов этого метода переводит нить в состояние TIMED_WAITING.
    По аналогии с методом wait() , wait(timeout) можно вызвать только внутри блока synchronized у объекта-мютекса, который был «залочен (заблокирован)» текущей нитью. Object monitor = getMonitor () ; synchronized (monitor) { … monitor. wait (500 ) ; … }

    При вызове метода wait() , текущая нить снимает блокировку с объекта monitor , и засыпает на 500 миллисекунд. Объект monitor может быть захвачен другой нитью.
    Через 500 миллисекунд нить проснется и если monitor не был занят, то захватит его и продолжит работу.
    Если монитор окажется занят другой нитью, текущая нить перейдет в состояние BLOCKED.

    В какое состояние перейдет нить, при вызове метода notify() ?

    Object monitor = getMonitor () ; synchronized (monitor) { … monitor. wait () ; … }
    После monitor.wait() , нить перейдет в состояние WAITING. Метод notify() , вызванный другой нитью у объекта monitor переведет нить из состояния WAITING в состояние RUNNABLE, если объект monitor не будет захвачен другой нитью, иначе в состояние BLOCKED.

    В какое состояние перейдет нить, при вызове метода notifyAll() ?

    NotifyAll() "пробудет" все нити. Одна из всех "спящих" (WAITING) нитей перейдет в состояние RUNNABLE, захватит монитор используемого объекта и продолжит свою работу. Остальные окажутся в состоянии BLOCKED. Как только первая "проснувшаяся" нить отпустит монитор, который все остальные ожидают, её участь повторит следующая нить (произвольная нить из состояния BLOCKED перейдет в состояние RUNNABLE). Это будет продолжаться до тех пор, пока все "пробужденные" нити не покинут состояния BLOCKED.

    Три нити в блоке synchronized вызвали wait() у объекта-мютекса. В какое состояние перейдут эти нити, если четвертая нить вызовет notifyAll() ?

    Две из них перейдут в состояние BLOCKED, одна в состояние RUNNABLE

    Чем отличается join(500) от wait(500) ?

    Несмотря на то, что и join(500) и wait(500) переведут текущую нить в состояние TIMED_WAITING, между ними существенные различия:
    join(500) вызывается у нити, wait(500) вызывается внутри синхронизированного блока у объекта, по которому данный блок синхронизирован.
    При вызове join(500) текущая нить будет ожидать 500 миллисекунд завершения нити, чей метод join() был вызван.
    Через 500 миллисекунд в обоих случаях нити продолжат работу.

    Чем отличается wait(500) от sleep(500) ?

    Sleep(500) вызывается у нити, wait(500) вызывается внутри синхронизированного блока у объекта, по которому данный блок синхронизирован.
    При вызове sleep(500) текущая нить будет ожидать 500 милисекунд, затем продолжит свою работу.
    При вызове wait(500) текущая нить снимет блокировку с синхронизированного объекта, и засыпает на 500 миллисекунд.

    В какое состояние перейдет нить при вызове метода yield() ?

    При вызове метода yield() – текущая нить «пропускает свой ход» и java сразу переключается на выполнение следующей нити. Нить из состояния running переходит в состояние ready . Состояния running & ready – это подсостояния состояния RUNNABLE.

    Допустим, вы пишете конвейер, в котором 2 потока, используя общий буфер, обрабатывают данные. Поток-producer эти данные создает, а поток-consumer их обрабатывает (Producer–consumer problem). Следующий код представляет собой самую простую модель: с помощью std::thread мы порождаем поток-consumer, a создавать данные мы будем в главном потоке.

    Void produce() { // создаем задачу и кладем в очередь } void consume() { // читаем данные из очереди и обрабатываем } int main(int , char **) { std::thread thr(consume); // порождаем поток produce(); // создаем данные для обработки thr.join(); // ждем завершения работы функции consume() return 0; }

    Опустим механизмы синхронизации двух потоков, и обратим внимание на функцию main() . Попробуйте догадаться, что с этим кодом не так, и как его исправить?

    Допустим, функция consume() бросает исключение. Поскольку это исключение генерируется в дочернем потоке, поймать и обработать его в главном потоке нельзя . Если во время развертывания стека дочернего потока не нашлось подходящего обработчика исключения, будет вызвана функция std::terminate() , которая по-умолчанию вызовет функцию abort() . Иными словами, если не обработать исключение в потоке, порожденном объектом thr , то программа завершит свою работу с ошибкой.

    С функцией produce() немного сложнее. Допустим, эта функция генерирует исключение. Первое, что хочется сделать, это обернуть тело main() в try-catch блок:

    Try { std::thread thr(consume); produce(); // бросает исключение thr.join(); } catch (...) { }

    Кажется, что проблема решена, но если вы попытаетесь запустить этот код, то программа упадет в любом случае. Почему так происходит? Давайте разбираться.

    std::thread

    Как вы уже, может быть, догадались, проблема не имеет отношение к конвейеру, а относится к правильному использованию потоков выполнения стандартной библиотеки в принципе. В частности следующая обобщенная функция равнозначна, и имеет те же проблемы:

    Void run(function f1, function f2) { std::thread thr(f1); f2(); thr.join(); } ... run(consume, produce); ...

    Прежде чем перейти к решению нашей задачи, давайте вкратце вспомним как работает std::thread .

    1) конструктор для инициализации:

    Template explicit thread (Fn&& fn, Args&&... args);

    При инициализации объекта std::thread создается новый поток, в котором запускается функция fn с возможными аргументами args . При успешном его создании, конкретный экземпляр объекта начинает представлять этот поток в родительском потоке, а в свойствах объекта выставляется флаг joinable .
    Запомним: joinable ~ объект связан с потоком.

    2) Ждем конца выполнения порожденного потока:

    Void thread::join();

    Этот метод блокирует дальнейшее выполнение родительского потока, до тех пока не будет завершен дочерний. После успешного выполнения, объект потока перестает его представлять, поскольку нашего потока больше не существует. Флаг joinable сбрасывается.

    3) Немедленно “отсоединяем” объект от потока:

    Void thread::detach();

    Это неблокирующий метод. Флаг joinable сбрасывается, а дочерний поток предоставлен сам себе и завершит свою работу когда-нибудь позже.

    4) Деструктор:

    Thread::~thread();

    Деструктор уничтожает объект. При этом если, у этого объекта стоит флаг joinable , то вызывается функция std::terminate() , которая по умолчанию вызовет функцию abort() .
    Внимание! Если мы создали объект и поток, но не вызвали join или detach , то программа упадет. В принципе, это логично – если объект до сих пор связан с потоком, то надо что-то с ним делать. А еще лучше – ничего не делать, и завершить программу (по крайней мере так решил комитет по стандарту).

    Поэтому при возникновении исключения в функции produce() , мы пытаемся уничтожить объект thr , который является joinable .

    Ограничения

    Почему же стандартный комитет решил поступить так и не иначе? Не лучше было бы вызвать в деструкторе join() или detach() ? Оказывается, не лучше. Давайте разберем оба этих случая.

    Допустим, у нас есть класс joining_thread , который так вызывает join() в своем деструкторе:

    Joining_thread::~joining_thread() { join(); }

    Тогда, прежде чем обработать исключение, мы должны будем подождать завершения работы дочернего потока, поскольку join() блокирует дальнейшее выполнение программы. А если так получилось, что порожденном потоке оказался в бесконечный цикл?

    Void consume() { while(1) { ... } } ... try { joining_thread thr(consume); throw std::exception(); } catch (...) { // может случится не скоро, или даже никогда }

    Хорошо, мы выяснили, что join() в деструкторе лучше не вызывать (до тех пор пока вы не уверены, что это корректная обработка события), поскольку это блокирующая операция. А что насчет detach() ? Почему бы не вызвать в деструкторе этот неблокирующий метод, дав главному потоку продолжить работу? Допустим у нас есть такой класс detaching_thread .

    Но тогда мы можем прийти к такой ситуации, когда порожденный поток пытается использовать ресурс, которого уже нет, как в следующей ситуации:

    Try { int data; detaching_thread th(consume, &data); // в данном случае consume принимает указатель на int в качестве аргумента throw std::exception() } catch (...) { // корректно обработаем исключение // consume продолжает исполняться, но ссылается на уже удаленный объект data }

    Таким образом, создатели стандарта решили переложить ответственность на программиста – в конце концов ему виднее, как программа должна обрабатывать подобные случаи. Исходя из всего этого, получается, что стандартная библиотека противоречит принципу – при создании std::thread мы сами должны позаботиться о корректном управлении ресурсами, то есть явно вызвать join или detach . По этой причине некоторые программисты советуют не использовать объекты std::thread. Так же как new и delete, std::thread предоставляет возможность построить на основе них более высокоуровневые инструменты.

    Решение

    Одним из таких инструментов является класс из библиотеки Boost boost::thread_joiner . Он соответствует нашему joining_thread в примере выше. Если вы можете позволить себе использовать сторонние библиотеки для работы с потоками, то лучше это сделать.


    В русской терминологии за термином Thread укрепился перевод "Поток". Хотя это слово также можно перевести как "Нить". Иногда в зарубежных учебных материалах понятие потока объясняется именно на нитях. Продолжим логический ряд - там где нити, там и клубок. А где клубок, там и кот. Сразу видно, что у переводчиков не было котов. Так и возникла путаница. Тем более что существуют другие потоки под термином Stream . Переводчики, вообще странный народ.

    Когда запускается любое приложение, то начинает выполняться поток, называемый главным потоком (main). От него порождаются дочерние потоки. Главный поток, как правило, является последним потоком, завершающим выполнение программы.

    Несмотря на то, что главный поток создаётся автоматически, им можно управлять через объект класса Thread . Для этого нужно вызвать метод currentThread() , после чего можно управлять потоком.

    Класс Thread содержит несколько методов для управления потоками.

    • getName() - получить имя потока
    • getPriority() - получить приоритет потока
    • isAlive() - определить, выполняется ли поток
    • join() - ожидать завершение потока
    • run() - запуск потока. В нём пишите свой код
    • sleep() - приостановить поток на заданное время
    • start() - запустить поток

    Получим информацию о главном потоке и поменяем его имя.

    Thread mainThread = Thread.currentThread(); mInfoTextView.setText("Текущий поток: " + mainThread.getName()); // Меняем имя и выводим в текстовом поле mainThread.setName("CatThread"); mInfoTextView.append("\nНовое имя потока: " + mainThread.getName());

    Имя у главного потока по умолчанию main , которое мы заменили на CatThread .

    Вызовем информацию о названии потока без указания метода.

    Thread mainThread = Thread.currentThread(); mInfoTextView.setText("Текущий поток: " + mainThread);

    В этом случае можно увидеть строчку Thread - имя потока, его приоритет и имя его группы.

    Создание собственного потока

    Создать собственный поток не сложно. Достаточно наследоваться от класса Thread .

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

    Public class MyThread extends Thread { public void run() { Log.d(TAG, "Mой поток запущен..."); } } public void onClick(View view) { MyThread myThread = new MyThread(); myThread.start(); }

    Как вариант, перенести вызов метода start() в конструктор.

    Public void onClick(View view) { MyThread myThread = new MyThread(); } public class MyThread extends Thread { // Конструктор MyThread() { // Создаём новый поток super("Второй поток"); Log.i(TAG, "Создан второй поток " + this); start(); // Запускаем поток } public void run() { Log.d(TAG, "Mой поток запущен..."); try { for (int i = 5; i > 0; i--) { Log.i(TAG, "Второй поток: " + i); Thread.sleep(500); } } catch (InterruptedException e) { Log.i(TAG, "Второй поток прерван"); } } }

    Создание потока с интерфейсом Runnable

    Есть более сложный вариант создания потока. Для создания нового потока нужно реализовать интерфейс Runnable . Вы можете создать поток из любого объекта, реализующего интерфейс Runnable и объявить метод run() .

    Внутри метода run() вы размещаете код для нового потока. Этот поток завершится, когда метод вернёт управление.

    Когда вы объявите новый класс с интерфейсом Runnable , вам нужно использовать конструктор:

    Thread(Runnable объект_потока, String имя_потока)

    В первом параметре указывается экземпляр класса, реализующего интерфейс. Он определяет, где начнётся выполнение потока. Во втором параметре передаётся имя потока.

    После создания нового потока, его нужно запустить с помощью метода start() , который, по сути, выполняет вызов метода run() .

    Создадим новый поток внутри учебного проекта в виде вложенного класса и запустим его.

    Package ru.alexanderklimov.expresscourse; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import static ru.alexanderklimov.expresscourse.R.id.textViewInfo; public class MainActivity extends AppCompatActivity { final String TAG = "ExpressCourse"; private Button mButton; private EditText mResultEditText; private TextView mInfoTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button) findViewById(R.id.buttonGetResult); mResultEditText = (EditText) findViewById(R.id.editText); mInfoTextView = (TextView) findViewById(textViewInfo); } public void onClick(View view) { new MyRunnable(); // создаём новый поток try { for (int i = 5; i > 0; i--) { Log.i(TAG, "Главный поток: " + i); Thread.sleep(1000); } } catch (InterruptedException e) { Log.i(TAG, "Главный поток прерван"); } } class MyRunnable implements Runnable { Thread thread; // Конструктор MyRunnable() { // Создаём новый второй поток thread = new Thread(this, "Поток для примера"); Log.i(TAG, "Создан второй поток " + thread); thread.start(); // Запускаем поток } // Обязательный метод для интерфейса Runnable public void run() { try { for (int i = 5; i > 0; i--) { Log.i(TAG, "Второй поток: " + i); Thread.sleep(500); } } catch (InterruptedException e) { Log.i(TAG, "Второй поток прерван"); } } } }

    Внутри конструктора MyRunnable() мы создаём новый объект класса Thread

    Thread = new Thread(this, "Поток для примера");

    В первом параметре использовался объект this , что означает желание вызвать метод run() этого объекта. Далее вызывается метод start() , в результате чего запускается выполнение потока, начиная с метода run() . В свою очередь метод запускает цикл для нашего потока. После вызова метода start() , конструктор MyRunnable() возвращает управление приложению. Когда главный поток продолжает свою работу, он входит в свой цикл. После этого оба потока выполняются параллельно.

    Можно запускать несколько потоков, а не только второй поток в дополнение к первому. Это может привести к проблемам, когда два потока пытаюсь работать с одной переменной одновременно.

    Ключевое слово syncronized - синхронизированные методы

    Для решения проблемы с потоками, которые могут внести путаницу, используется синхронизация.

    Метод может иметь модификатор syncronized . Когда поток находится внутри синхронизированного метода, все другие потоки, которые пытаются вызвать его в том же экземпляре, должны ожидать. Это позволяет исключить путаницу, когда несколько потоков пытаются вызвать метод.

    Syncronized void meow(String msg);

    Кроме того, ключевое слово syncronized можно использовать в качестве оператора. Вы можете заключить в блок syncronized вызовы методов какого-нибудь класса:

    Syncronized(объект) { // операторы, требующие синхронизации }

    Looper

    Поток имеет в своём составе сущности Looper , Handler , MessageQueue .

    Каждый поток имеет один уникальный Looper и может иметь много Handler .

    Считайте Looper вспомогательным объектом потока, который управляет им. Он обрабатывает входящие сообщения, а также даёт указание потоку завершиться в нужный момент.

    Поток получает свой Looper и MessageQueue через метод Looper.prepare() после запуска. Looper.prepare() идентифицирует вызывающий потк, создаёт Looper и MessageQueue и связывает поток с ними в хранилище ThreadLocal . Метод Looper.loop() следует вызывать для запуска Looper . Завершить его работу можно через метод looper.quit() .

    Class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); } }

    Используйте статический метод getMainLooper() для доступа к Looper главного потока:

    Looper mainLooper = Looper.getMainLooper();

    Создадим два потока. Один запустим в основном потоке, а второй отдельно от основного. Нам будет достаточно двух кнопок и метки.

    Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.