Учтивый thread. Класс Thread. Для чего используется класс Future

  1. Какие приоритеты нитей бывают?

    Ответ на этот вопрос есть в лекциях JavaRush.

    Для оптимизации параллельной работы нитей в Java имеется возможность устанавливать приоритеты нитей. Нити с большим приоритетом имеют преимущество в получении времени процессора перед нитями с более низким приоритетом.

    Работа с приоритетами обеспечивается следующими методами класса Thread:

    public final void setPriority(int newPriority)

    Устанавливает приоритет нити.

    public final int getPriority()

    Позволяет узнать приоритет нити.

    Значение параметра в методе setPriority не может произвольным. Оно должно находиться в пределах от MIN_PRIORITY до MAX_PRIORITY . При своем создании нить имеет приоритет NORM_PRIORITY .

    MIN_PRIORITY = 1.
    NORM_PRIORITY =5.
    MAX_PRIORITY = 10.

  2. Можно ли остановить нить, снизив ее приоритет до 0?

    Ответ в статье: «Топ 50 вопросов на собеседовании. Тема: Многопоточность (Multithreading)»

    На форуме нашел.

    Есть вариант этой статьи на английском языке: Top 50 Java Thread Interview Questions Answers for Freshers, Experienced Programmers

    Java предоставляет богатые API для всего, но, по иронии судьбы, не предоставляет удобных способов остановки нити. В JDK 1.0 было несколько управляющих методов, например stop() , suspend() и resume() , которые были помечены как deprecated в будущих релизах из-за потенциальных угроз взаимной блокировки, с тех пор разработчики Java API не предприняли попыток представить стойкий, ните-безопасный и элегантный способ остановки нитей. Программисты в основном полагаются на факт того, что нить останавливается сама, как только заканчивает выполнять методы run() или call() . Для остановки вручную, программисты пользуются преимуществом volatile boolean переменной и проверяют её значение в каждой итерации, если в методе run() есть циклы, или прерывают нити методом interrupt() для внезапной отмены заданий.

    Конкретно по вопросу: Нигде не видел, чтобы кто-то приоритет выставлял в 0.

    Если кто знает об этом что-нибудь, то напишите в комментариях.

    Зачем нужен класс ThreadGroup ?

    ThreadGroup представляет собой набор нитей, которые так же могут содержать в себе другие группы потоков. Группа нитей образует дерево, в котором каждая другая группа нитей имеет родителя (кроме исходной). Поток имеет право доступа к данным из своей группы нитей, но не имеет такого доступа к другим группам или к родительской группе потоков.

    В какой группе нитей состоит main-thread ?

    Нигде не нашел)) Подскажите где это есть))

    Что такое паттерн ThreadPool ?

    На это есть выдержка из статьи на википедии:

    In computer programming, the thread pool pattern (also replicated workers or worker-crew model) is where a number of threads are created to perform a number of tasks, which are usually organized in a queue. The results from the tasks being executed might also be placed in a queue, or the tasks might return no result (for example, if the task is for animation). Typically, there are many more tasks than threads. As soon as a thread completes its task, it will request the next task from the queue until all tasks have been completed. The thread can then terminate, or sleep until there are new tasks available.

    The number of threads used is a parameter that can be tuned to provide the best performance. Additionally, the number of threads can be dynamic based on the number of waiting tasks. For example, a web server can add threads if numerous web page requests come in and can remove threads when those requests taper down. The cost of having a larger thread pool is increased resource usage. The algorithm used to determine when to create or destroy threads will have an impact on the overall performance:

    • create too many threads, and resources are wasted and time also wasted creating any unused threads
    • destroy too many threads and more time will be spent later creating them again
    • creating threads too slowly might result in poor client performance (long wait times)

    В компьютерном программировании есть модель пула потоков, где определенное число потоков создается для выполнения целого ряда задач, которые обычно организуются в очереди. Результаты от выполненных задач также могут быть помещены в очередь, либо задачи могут не возвращать никакого результата (например, если задача для анимации).

    Как правило, существует гораздо больше задач, чем потоков. Как только поток завершит свою задачу, он будет запрашивать следующую задачу из очереди, пока все задачи не будут завершены. Поток может затем прерваться или заснуть. Количество используемых потоков, это параметр, который может быть настроен, для обеспечения наилучшей производительности. Кроме того, число потоков может быть динамическим на основе количества возникающих задач. Например, веб-сервер может добавлять потоки, если запросы многочисленных веб-страниц приходят и может удалить потоки, когда этих запросов становится меньше. С увеличением размера пула потоков увеличивается использование ресурсов компьютера. Алгоритм, используемый для определения того, когда создавать или уничтожать потоки, будет иметь влияние на общую производительность: - Создать слишком много потоков значит тратить ресурсы и время впустую.

    Уничтожить слишком много потоков и больше времени будет потрачено позже снова для их создания - Создание потоков слишком медленно, может привести к снижению производительности клиента.

    Зачем нужен класс ThreadPoolExecutor ?

    public class ThreadPoolExecutor extends AbstractExecutorService

    ExecutorService это выполняет каждую представленную задачу, используя один возможно из нескольких объединенных в пул потоков, обычно сконфигурированное использование Executors методы фабрики.

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

    Чтобы быть полезным через широкий диапазон контекстов, этот класс обеспечивает много корректируемых параметров и рычагов расширяемости. Однако, программистов убеждают использовать более удобное Executors методы фабрики Executors.newCachedThreadPool() (неограниченный пул потоков, с автоматическим восстановлением потока), Executors.newFixedThreadPool(int) (пул потоков фиксированного размера) и Executors.newSingleThreadExecutor() (единственный фоновый поток), которые предварительно конфигурируют настройки для наиболее распространенных сценариев использования.

    Сколько способов создать нить вы знаете?

    На уровне языка есть два способа создания нити. Объект класса java.lang.Thread представляет собой нить, но ей требуется задача для исполнения, которая является объектом, реализующим интерфейс java.lang.Runnable . Так как класс Thread реализует интерфейс Runnable , вы можете переопределить метод run() унаследовав ваш класс от Thread или реализовав в нём интерфейс Runnable .

    Для чего используется класс Future ?

    Future хранит результат асинхронного вычисления. Вы можете запустить вычисление, предоставив кому-либо объект Future , и забыть о нем. Владелец объекта Future может получить результат, когда он будет готов.

    В чем преимущества Callable над Runnable ?

    Интерфейс Callable гораздо больше подходит для создания задач, предназначенных для параллельного выполнения, нежели интерфейс Runnable или тем более класс Thread . При этом стоит отметить, что возможность добавить подобный интерфейс появилась только начиная с версии Java 5, так как ключевая особенность интерфейса Callable – это использование параметризованных типов (generics), как показано в листинге.

    Листинг Создание задачи с помощью интерфейса Callable 10 1 import java. util. concurrent. Callable; 11 2 public class CallableSample implements Callable { 12 3 public String call () throws Exception { 13 4 if (какое- то условие) { 14 5 throw new IOException ("error during task processing" ) ; 15 6 } 16 7 System. out. println ("task is processing" ) ; 17 8 return "result " ; 18 9 } 19 10 }

    Сразу необходимо обратить внимание на строку 2, где указано, что интерфейс Callable является параметризованным, и его конкретная реализация – класс CallableSample , зависит от типа String . На строке 3 приведена сигнатура основного метода call в уже параметризованном варианте, так как в качестве типа возвращаемого значения также указан тип String . Фактически это означает, что была создана задача, результатом выполнения которой будет объект типа String (см. строку 8). Точно также можно создать задачу, в результате работы которой в методе call будет создаваться и возвращаться объект любого требуемого типа. Такое решение значительно удобнее по сравнению с методом run в интерфейсе Runnable , который не возвращает ничего (его возвращаемый тип – void) и поэтому приходится изобретать обходные пути, чтобы извлечь результат работы задачи.

    Еще одно преимущество интерфейса Callable – это возможность «выбрасывать» исключительные ситуации, не оказывая влияния на другие выполняющиеся задачи. На строке 3 указано, что из метода может быть «выброшена» исключительная ситуация типа Exception , что фактически означает любую исключительную ситуацию, так как все исключения являются потомками java.lang.Exception . На строке 5 эта возможность используется для создания контролируемой (checked) исключительной ситуации типа IOException . Метод run интерфейса Runnable вообще не допускал выбрасывания контролируемых исключительных ситуаций, а выброс неконтролируемой (runtime) исключительной ситуации приводил к остановке потока и всего приложения.

    Можно ли отменить выполнение задачи, если использовать класс Future ?

    Исходя из этой дискуссии , поднятой на хабре, выходит, что нельзя.

    У Future есть метод Future.cancel(boolean) , который должен отменить выполнение задачи. Но если задача уже начала выполняться, вызов Future.cancel(true) на самом деле не остановит ее. В недрах реализации FutureTask выполняется код:

    if (mayInterruptIfRunning) { Thread r = runner; if (r != null) r. interrupt () ; }

    Т.е. опять потоку, в котором выполняется задача, всего лишь рекомендуется прекратить выполнение. К тому же, мы не имеем даже возможности узнать выполняется ли задача в данный момент или нет. Есть, метод Future.isDone() , но опять мимо, он возвращает true не только когда задача завершила выполнение, а сразу после вызова Future.cancel() , даже если задача все еще выполняется (ведь Future.cancel(true) не останавливает задачу которая уже начала выполняться).

    Хорошо, если мы сами пишем весь код, тогда можно в нужных местах аккуратно обрабатывать Thread.isInterrupted() и все будет ОК. Но если мы запускаем сторонний код? Если у нас есть сервер расширяемый с помощью плагинов? Какой-нибудь криво написанный плагин может запросто привести к неработоспособному состоянию весь сервер ведь мы не можем корректно прервать выполнение зависшего плагина.

В какие состояния может перейти нить, при входе в блок 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

    Класс Thread является самым элементарным из всех типов пространства имен System.Threading. Этот класс представляет объектно-ориентированную оболочку вокруг заданного пути выполнения внутри определенного AppDomain. Этот тип также определяет набор методов (как статических, так и уровня экземпляра), которые позволяют создавать новые потоки внутри текущего AppDomain, а также приостанавливать, останавливать и уничтожать определенный поток. Список основных статических членов приведен ниже:

    CurrentContext

    Это свойство только для чтения возвращает контекст, в котором в данный момент выполняется поток

    CurrentThread

    Это свойство только для чтения возвращает ссылку на текущий выполняемый поток

    GetDomain(), GetDomainID() Sleep()

    Этот метод приостанавливает текущий поток на заданное время

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

    Член уровня экземпляра Назначение
    IsAlive Возвращает булевское значение, указывающее на то, запущен ли поток (и еще не прерван и не отменен)
    IsBackground Получает или устанавливает значение, указывающее, является ли данный поток "фоновым" (подробнее объясняется далее)
    Name Позволяет вам установить дружественное текстовое имя потока
    Priority Получает или устанавливает приоритет потока, который может принимать значение из перечисления ThreadPriority
    ThreadState Получает состояние данного потока, которому может быть присвоено значение из перечисления ThreadState
    Abort() Инструктирует CLR прервать поток, как только это будет возможно
    Interrupt() Прерывает (т.е. приостанавливает) текущий поток на заданный период ожидания
    Join() Блокирует вызывающий поток до тех пор, пока указанный поток (тот, в котором вызван Join()) не завершится
    Resume() Возобновляет ранее приостановленный поток
    Start() Инструктирует CLR запустить поток как можно скорее
    Suspend() Приостанавливает поток. Если поток уже приостановлен, вызов Suspend() не дает эффекта

    Получение статистики о текущем потоке

    Вспомните, что точка входа исполняемой сборки (т.е. метод Main()) запускается в первичном потоке выполнения. Чтобы проиллюстрировать базовое применение типа Thread, предположим, что имеется новое консольное приложение. Как известно, статическое свойство Thread.CurrentThread извлекает объект Thread, представляющий текущий выполняющийся поток. После получения текущего потока можно вывести разнообразную статистику о нем:

    Using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ConsoleApplication1 { class Program { static void Main() { Console.Title = "Информация о главном потоке программы"; Thread thread = Thread.CurrentThread; thread.Name = "MyThread"; Console.WriteLine(@"Имя домена приложения: {0} ID контекста: {1} Имя потока: {2} Запущен ли поток? {3} Приоритет потока: {4} Состояние потока: {5}", Thread.GetDomain().FriendlyName, Thread.CurrentContext.ContextID, thread.Name, thread.IsAlive, thread.Priority, thread.ThreadState); Console.ReadLine(); } } }

    Хотя этот код более-менее очевиден, обратите внимание, что класс Thread поддерживает свойство по имени Name. Если не установить его значение явно, то Name вернет пустую строку. Присваивание дружественного имени конкретному объекту Thread может значительно упростить отладку. Во время сеанса отладки в Visual Studio 2010 можно открыть окно Threads (Потоки), выбрав пункт меню Debug --> Windows --> Threads (Отладка --> Окна --> Потоки).

    Обратите внимание, что в типе Thread определено свойство по имени Priority. По умолчанию все потоки имеют уровень приоритета Normal. Однако это можно изменить в любой момент жизненного цикла потока, используя свойство Thread.Priority и связанное с ним перечисление System.Threading.ThreadPriority:

    Public enum ThreadPriority { Lowest, BelowNormal, Normal, // Значение по умолчанию. AboveNormal, Highest }

    При установке уровня приоритета потока равным значению, отличному от принятого по умолчанию (ThreadPriority.Normal), следует иметь в виду, что это не предоставляет прямого контроля над тем, как планировщик потоков будет переключать потоки между собой. На самом деле уровень приоритета потока предоставляет CLR подсказку относительно важности действий потока. Таким образом, поток с уровнем приоритета ThreadPriority.Highest не обязательно гарантированно получит наивысший приоритет.