Описание класса thread


Содержание

Класс Thread

Класс Thread используется для создания нового потока.

Начнем с рассмотрения примера, демонстрирующего создание нового потока:

Пример 1. Расширение класса java.lang.Thread

Метод start() — это специальный метод, создающий новый поток, в котором будет выполняться метод run(). Метод start() не может быть вызван дважды для одного и того же объекта, даже если поток уже завершен. Иначе будет выброшено и исключение IllegalThreadStateException.

Пример 2. Перегрузка метода run()

Конструкторы класса Thread:

Пример 3. Реализация интерфейса java.lang.Runnable

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

Многопоточное программирование в Java 8. Часть первая. Параллельное выполнение кода с помощью потоков

Добро пожаловать в первую часть руководства по параллельному программированию в Java 8. В этой части мы на простых примерах рассмотрим, как выполнять код параллельно с помощью потоков, задач и сервисов исполнителей.

Впервые Concurrency API был представлен вместе с выходом Java 5 и с тех пор постоянно развивался с каждой новой версией Java. Большую часть примеров можно реализовать на более старых версиях, однако в этой статье я собираюсь использовать лямбда-выражения. Если вы все еще не знакомы с нововведениями Java 8, рекомендую посмотреть мое руководство.

Потоки и задачи

Все современные операционные системы поддерживают параллельное выполнение кода с помощью процессов и потоков. Процесс — это экземпляр программы, который запускается независимо от остальных. Например, когда вы запускаете программу на Java, ОС создает новый процесс, который работает параллельно другим. Внутри процессов мы можем использовать потоки, тем самым выжав из процессора максимум возможностей.

Потоки (threads) в Java поддерживаются начиная с JDK 1.0. Прежде чем запустить поток, ему надо предоставить участок кода, который обычно называется «задачей» (task). Это делается через реализацию интерфейса Runnable , у которого есть только один метод без аргументов, возвращающий void — run() . Вот пример того, как это работает:

Поскольку интерфейс Runnable функциональный, мы можем использовать лямбда-выражения, которые появились в Java 8. В примере мы создаем задачу, которая выводит имя текущего потока на консоль, и запускаем ее сначала в главном потоке, а затем — в отдельном.

Результат выполнения этого кода может выглядеть так:

Из-за параллельного выполнения мы не можем сказать, будет наш поток запущен до или после вывода «Done!» на экран. Эта особенность делает параллельное программирование сложной задачей в больших приложениях.

21 ноября в 19:30, Санкт-Петербург, беcплатно

Потоки могут быть приостановлены на некоторое время. Это весьма полезно, если мы хотим сэмулировать долго выполняющуюся задачу. Например, так:

Когда вы запустите этот код, вы увидите секундную задержку между выводом первой и второй строки на экран. TimeUnit — полезный класс для работы с единицами времени, но то же самое можно сделать с помощью Thread.sleep(1000) .

Работать с потоками напрямую неудобно и чревато ошибками. Поэтому в 2004 году в Java 5 добавили Concurrency API. Он находится в пакете java.util.concurrent и содержит большое количество полезных классов и методов для многопоточного программирования. С тех пор Concurrency API непрерывно развивался и развивается.

Давайте теперь подробнее рассмотрим одну из самых важных частей Concurrency API — сервис исполнителей (executor services).

Исполнители

Concurrency API вводит понятие сервиса-исполнителя (ExecutorService) — высокоуровневую замену работе с потоками напрямую. Исполнители выполняют задачи асинхронно и обычно используют пул потоков, так что нам не надо создавать их вручную. Все потоки из пула будут использованы повторно после выполнения задачи, а значит, мы можем создать в приложении столько задач, сколько хотим, используя один исполнитель.

Вот как будет выглядеть наш первый пример с использованием исполнителя:

Класс Executors предоставляет удобные методы-фабрики для создания различных сервисов исполнителей. В данном случае мы использовали исполнитель с одним потоком.

Результат выглядит так же, как в прошлый раз. Но у этого кода есть важное отличие — он никогда не остановится. Работу исполнителей надо завершать явно. Для этого в интерфейсе ExecutorService есть два метода: shutdown() , который ждет завершения запущенных задач, и shutdownNow() , который останавливает исполнитель немедленно.

Вот как я предпочитаю останавливать исполнителей:

Исполнитель пытается завершить работу, ожидая завершения запущенных задач в течение определенного времени (5 секунд). По истечении этого времени он останавливается, прерывая все незавершенные задачи.

Callable и Future

Кроме Runnable , исполнители могут принимать другой вид задач, который называется Callable . Callable — это также функциональный интерфейс, но, в отличие от Runnable , он может возвращать значение.

Давайте напишем задачу, которая возвращает целое число после секундной паузы:

Callable-задачи также могут быть переданы исполнителям. Но как тогда получить результат, который они возвращают? Поскольку метод submit() не ждет завершения задачи, исполнитель не может вернуть результат задачи напрямую. Вместо этого исполнитель возвращает специальный объект Future, у которого мы сможем запросить результат задачи.

После отправки задачи исполнителю мы сначала проверяем, завершено ли ее выполнение, с помощью метода isDone() . Поскольку задача имеет задержку в одну секунду, прежде чем вернуть число, я более чем уверен, что она еще не завершена.

Вызов метода get() блокирует поток и ждет завершения задачи, а затем возвращает результат ее выполнения. Теперь future.isDone() вернет true , и мы увидим на консоли следующее:

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

Вы, возможно, заметили, что на этот раз мы создаем сервис немного по-другому: с помощью метода newFixedThreadPool(1) , который вернет исполнителя с пулом в один поток. Это эквивалентно вызову метода newSingleThreadExecutor() , однако мы можем изменить количество потоков в пуле.

Таймауты

Любой вызов метода future.get() блокирует поток до тех пор, пока задача не будет завершена. В наихудшем случае выполнение задачи не завершится никогда, блокируя ваше приложение. Избежать этого можно, передав таймаут:

Выполнение этого кода вызовет TimeoutException :

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


InvokeAll

Исполнители могут принимать список задач на выполнение с помощью метода invokeAll() , который принимает коллекцию callable-задач и возвращает список из Future .

В этом примере мы использовали функциональные потоки Java 8 для обработки задач, возвращенных методом invokeAll . Мы прошлись по всем задачам и вывели их результат на консоль. Если вы не знакомы с потоками (streams) Java 8, смотрите мое руководство.

InvokeAny

Другой способ отдать на выполнение несколько задач — метод invokeAny() . Он работает немного по-другому: вместо возврата Future он блокирует поток до того, как завершится хоть одна задача, и возвращает ее результат.

Чтобы показать, как работает этот метод, создадим метод, эмулирующий поведение различных задач. Он будет возвращать Callable , который вернет указанную строку после необходимой задержки:

Используем этот метод, чтобы создать несколько задач с разными строками и задержками от одной до трех секунд. Отправка этих задач исполнителю через метод invokeAny() вернет результат задачи с наименьшей задержкой. В данном случае это «task2»:

Илон Маск рекомендует:  Что такое код locksegment

В примере выше использован еще один вид исполнителей, который создается с помощью метода newWorkStealingPool() . Этот метод появился в Java 8 и ведет себя не так, как другие: вместо использования фиксированного количества потоков он создает ForkJoinPool с определенным параллелизмом (parallelism size), по умолчанию равным количеству ядер машины.

ForkJoinPool впервые появился в Java 7, и мы рассмотрим его подробнее в следующих частях нашего руководства. А теперь давайте посмотрим на исполнители с планировщиком (scheduled executors).

Исполнители с планировщиком

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

ScheduledExecutorService способен запускать задачи один или несколько раз с заданным интервалом.

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

Когда мы передаем задачу планировщику, он возвращает особый тип Future — ScheduledFuture , который предоставляет метод getDelay() для получения оставшегося до запуска времени.

У исполнителя с планировщиком есть два метода для установки задач: scheduleAtFixedRate() и scheduleWithFixedDelay() . Первый устанавливает задачи с определенным интервалом, например, в одну секунду:

Кроме того, он принимает начальную задержку, которая определяет время до первого запуска.

Обратите внимание, что метод scheduleAtFixedRate() не берет в расчет время выполнения задачи. Так, если вы поставите задачу, которая выполняется две секунды, с интервалом в одну, пул потоков рано или поздно переполнится.

В этом случае необходимо использовать метод scheduleWithFixedDelay() . Он работает примерно так же, как и предыдущий, но указанный интервал будет отсчитываться от времени завершения предыдущей задачи.

В этом примере мы ставим задачу с задержкой в одну секунду между окончанием выполнения задачи и началом следующей. Начальной задержки нет, и каждая задача выполняется две секунды. Так, задачи будут запускаться на 0, 3, 6, 9 и т. д. секунде. Как видите, метод scheduleWithFixedDelay() весьма полезен, если мы не можем заранее сказать, сколько будет выполняться задача.

Это была первая часть серии статей про многопоточное программирование. Настоятельно рекомендую разобрать вышеприведенные примеры самостоятельно. Все они доступны на GitHub. Можете смело форкать репозиторий и добавлять его в избранное.

Надеюсь, вам понравилась статья. Если у вас возникли какие-либо вопросы, вы можете задать их в твиттере.

Класс Thread и интерфейс Runnable. Жизненный цикл потока (Java)

К большинству современных распределенных приложений (Rich Client) и веб-приложений (Thin Client) выдвигаются требования одновременной поддержки многих пользователей, каждому из которых выделяется отдельный поток, а также разделения и параллельной обработки информационных ресурсов.
Потоки — средство, которое помогает организовать одновременное выполнение нескольких задач, каждой в независимом потоке. Потоки представляют собой экземпляры классов, каждый из которых запускается и функционирует самостоятельно, автономно (или относительно автономно) от главного потока выполнения программы. Существует два способа создания и запуска потока: на основе расширения класса Thread или реализации интерфейса Runnable:

При реализации интерфейса Runnable необходимо определить его единственный абстрактный метод run(). Например:

Запуск двух потоков для объектов классов TalkThread непосредственно и WalkRunnable через инициализацию экземпляра Thread приводит к выводу строк: Talking Walking. Порядок вывода, как правило, различен при нескольких запусках приложения. Интерфейс Runnable не имеет метода start(), а только единственный метод run(). Поэтому для запуска такого потока, как WalkRunnable следует создать экземпляр класса Thread с передачей экземпляра WalkRunnable его конструктору. Однако при прямом вызове метода run() поток не запустится, выполнится только тело самого метода.

Жизненный цикл потока

При выполнении программы объект класса Thread может быть в одном из четырех основных состояний: «новый», «работоспособный», «неработоспособный» и «пассивный». При создании потока он получает состояние «новый» (NEW) и не выполняется. Для перевода потока из состояния «новый» в состояние «работоспособный» (RUNNABLE) следует выполнить метод start(), который вызывает метод run() — основной метод потока.

Поток может находиться в одном из состояний, соответствующих элементам статически вложенного перечисления Thread.State:

NEW — поток создан, но еще не запущен;

RUNNABLE — поток выполняется;

BLOCKED — поток блокирован;

WAITING — поток ждет окончания работы другого потока;

TIMED_WAITING — поток некоторое время ждет окончания другого потока;

TERMINATED — поток завершен.

Получить текущее значение состояния потока можно вызовом метода getState().

Поток переходит в состояние «неработоспособный» в режиме ожидания (WAITING) вызовом методов join(), wait(), suspend() (deprecated-метод) или методов ввода/вывода, которые предполагают задержку. Для задержки потока на некоторое время (в миллисекундах) можно перевести его в режим ожидания по времени TIMED_WAITING) с помощью методов yield(),sleep(longmillis), join(long timeout) и wait(long timeout), при выполнении которых может генерироваться прерывание InterruptedException. Вернуть потоку работоспособность после вызова метода suspend() можно методом resume() (deprecatedметод), а после вызова метода wait() — методами notify()или notifyAll(). Поток переходит в «пассивное» состояние (TERMINATED), если вызваны методы interrupt(), stop() (deprecated-метод) или метод run() завершил выполнение, и запустить его повторно уже невозможно. После этого, чтобы запустить поток, необходимо создать новый объект потока. Метод interrupt() успешно завершает поток, если он находится в состоянии «работоспособный». Если же поток неработоспособен, например, находится в состоянии TIMED_WAITING,
то метод инициирует исключение InterruptedException. Чтобы это не происходило, следует предварительно вызвать метод isInterrupted(), который проверит возможность завершения работы потока. При разработке не следует использовать методы принудительной остановки потока, так как возможны проблемы с закрытием ресурсов и другими внешними объектами.

Методы suspend(), resume() иstop() являются deprecated-методами и запрещены к использованию, так как они не являются в полной мере «потокобезопасными».

Runnable и Thread

В Java многопоточность программы организуется с помощью интерфейса Runnable и класса Thread, который наследуется от Runnable. Первый способ более гибкий, второй – проще.

Та часть кода, которая должна выполняться в отдельном потоке, выносится в свой класс, имеющий переопределенный метод run(). Код метода run() выполняется, когда к объекту типа Thread применяется метод start(). Непосредственный вызов run() новый поток не создает.

Здесь обработка исключений необходима из-за статического метода sleep(), который приостанавливает выполнение текущего потока. Данный метод часто используют в дочерних потоках, когда они должны выполнять какое-либо действие постоянно, но не бесперебойно. Например, периодически проверять доступность ресурса.


Метод join() заставляет текущий поток ждать завершения нити, к которой применяется. Только после этого текущий поток может продолжить выполнение своего кода.

В данном случае мы создаем класс-наследник от Runnable. Объект типа Runnable или его производное передается в конструктор объекта типа Thread. После этого поток запускается.

Другой вариант – когда пользовательский класс является наследником Thread:

Этот вариант не подходит, если класс для организации отдельного потока должен наследоваться от другого класса (не Thread). Поскольку в Java нет множественного наследования классов, приходится использовать наследование от интерфейса Runnable. Также данный подход не дает возможности запускать несколько потоков на основе одного объекта. Так в первом примере мы могли бы передать единственный объект anotherRun в несколько объектов типа Thread.

Напомним, библиотечный класс Thread сам является наследником Runnable.

Если в отдельный поток обособляется небольшая подзадача, можно использовать неименованный класс:

Прерывание потоков

Для прерывания выполнения нити, если это необходимо, используется метод interrupt(), который устанавливает переменную isInterrupt в значение true. К коде пользовательского класса, унаследованного от Runnable/Thread, это переменная должна проверяться. Отсюда следует, что на самом деле в Java нет возможности прервать поток извне, поток может остановиться только сам.

С другой стороны, в метод sleep() уже встроена проверка переменной isInterrupt, поэтому проверку вручную опускают. Если sleep() считывает наличие прерывания, то генерирует исключение.

В примере основной поток ожидает ввод данных, в это время выполняется вторая нить. Но как только вы нажмете Enter, выполнится метод interrupt(). В свою очередь метод sleep() прочитает значение переменной isInterrupt класса Thread и сгенерирует исключение InterruptedException.

Если sleep() не используется, то isInterrupt проверяется вручную методом isInterrupted(). Следующий пример содержит ошибку, приводящую к зацикливанию:

Мы могли бы ожидать, что через 2 секунды сработает метод interrupt(), который прервет дочернюю нить. Однако, поскольку в ней не проверяется значение isInterrupt, цикл продолжает работать. Корректный код может выглядеть так:

При наследовании от Runnable текущий поток через this получить нельзя. Его получают, вызывая соответствующий метод класса Thread:

Илон Маск рекомендует:  Сортировка столбцов в таблице без перезагрузки

В чем разница между Thread и Runnable?

В чем разница между Thread и Thread (Runnable)?

Иными словами, какой плюс от того, что поток Thread будет реализован через интерфейс Runnable? Это типа поток в потоке или какие-то доп. функции будут? К примеру, доступ к UI.

6 ответов 6

@Futurama, в документации совершенно четко прописан ответ на Ваш вопрос.

Читаем (жирный шрифт — это выделено мной):

There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows:

The following code would then create a thread and start it running:

Суть в том, что мы переопределяем метод run класса Thread, который вызывается из метода start класса Thread, который мы и вызываем для запуска потока (исполнения нашей программы в новом потоке).

The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started. The same example in this other style looks like the following:

The following code would then create a thread and start it running:

В принципе, как говориться — «те же яйца, вид сбоку».

Если вдуматься, то суть та же. Создается (подготавливается к запуску) новый поток (класс Thread — структура данных внутри JVM). В эту структуру заносится адрес функции (последовательности инструкций JVM), которая представляет программу, которую мы хотим выполнять в новом потоке. Вызов метода .start класса Thread (непосредственно или опосредовано, как унаследованного в первом случае) запустит новый поток (как именно — зависит от ОС в которой работает конкретная JVM).

Собеседование по Java — многопоточность (вопросы и ответы)

Вопросы и ответы для собеседования Java по теме — многопоточность.

К списку вопросов по всем темам

Вопросы

1. Дайте определение понятию “процесс”.
2. Дайте определение понятию “поток”.
3. Дайте определение понятию “синхронизация потоков”.
4. Как взаимодействуют программы, процессы и потоки?
5. В каких случаях целесообразно создавать несколько потоков?
6. Что может произойти если два потока будут выполнять один и тот же код в программе?
7. Что вы знаете о главном потоке программы?
8. Какие есть способы создания и запуска потоков?
9. Какой метод запускает поток на выполнение?
10. Какой метод описывает действие потока во время выполнения?
11. Когда поток завершает свое выполнение?
12. Как синхронизировать метод?
13. Как принудительно остановить поток?
14. Дайте определение понятию “поток-демон”.
15. Как создать поток-демон?
16. Как получить текущий поток?
17. Дайте определение понятию “монитор”.
18. Как приостановить выполнение потока?
19. В каких состояниях может пребывать поток?
20. Что является монитором при вызове нестатического и статического метода?
21. Что является монитором при выполнении участка кода метода?
22. Какие методы позволяют синхронизировать выполнение потоков?
23. Какой метод переводит поток в режим ожидания?
24. Какова функциональность методов notify и notifyAll?
25. Что позволяет сделать метод join?
26. Каковы условия вызова метода wait/notify?
27. Дайте определение понятию “взаимная блокировка”.
28. Чем отличаются методы interrupt, interrupted, isInterrupted?
29. В каком случае будет выброшено исключение InterruptedException, какие методы могут его выбросить?
30. Модификаторы volatile и метод yield().
31. Пакет java.util.concurrent
32. Есть некоторый метод, который исполняет операцию i++. Переменная i типа int. Предполагается, что код будет исполнятся в многопоточной среде. Следует ли синхронизировать блок?
33. Что используется в качестве mutex, если метод объявлен static synchronized? Можно ли создавать новые экземпляры класса, пока выполняется static synchronized метод?
34. Предположим в методе run возник RuntimeException, который не был пойман. Что случится с потоком? Есть ли способ узнать о том, что Exception произошел (не заключая все тело run в блок try-catch)? Есть ли способ восстановить работу потока после того как это произошло?
35. Какие стандартные инструменты Java вы бы использовали для реализации пула потоков?
36.Что такое ThreadGroup и зачем он нужен?
37.Что такое ThreadPool и зачем он нужен?
38.Что такое ThreadPoolExecutor и зачем он нужен?
39.Что такое «атомарные типы» в Java?
40.Зачем нужен класс ThreadLocal?
41.Что такое Executor?
42.Что такое ExecutorService?
43.Зачем нужен ScheduledExecutorService?

Ответы

1. Дайте определение понятию “процесс”.

Процесс — это совокупность кода и данных, разделяющих общее виртуальное адресное пространство. Процессы изолированы друг от друга, поэтому прямой доступ к памяти чужого процесса невозможен (взаимодействие между процессами осуществляется с помощью специальных средств). Для каждого процесса ОС создает так называемое «виртуальное адресное пространство», к которому процесс имеет прямой доступ. Это пространство принадлежит процессу, содержит только его данные и находится в полном его распоряжении. Операционная система же отвечает за то, как виртуальное пространство процесса проецируется на физическую память.

Многопоточность в Java: http://habrahabr.ru/post/164487/

2. Дайте определение понятию “поток”.

Один поток («нить» или «трэд») – это одна единица исполнения кода. Каждый поток последовательно выполняет инструкции процесса, которому он принадлежит, параллельно с другими потоками этого процесса.

Thinking in Java.Параллельное выполнение. http://wikijava.it-cache.net/index.php@title=Glava_17_Thinking_in_Java_4th_edition.html

3. Дайте определение понятию “синхронизация потоков”.

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

Java поддерживает несколько потоков для выполнения. Это может привести к тому, что два или более потока получат доступ к одному и тому же полю или объекту. Синхронизация — это процесс, который позволяет выполнять все параллельные потоки в программе синхронно. Синхронизация позволяет избежать ошибок согласованности памяти, вызванных непоследовательным доступом к общей памяти.
Когда метод объявлен как синхронизированный — нить держит монитор для объекта, метод которого исполняется. Если другой поток выполняет синхронизированный метод, ваш поток заблокируется до тех пор, пока другой поток не отпустит монитор.
Синхронизация достигается в Java использованием зарезервированного слова synchronized. Вы можете использовать его в своих классах определяя синхронизированные методы или блоки. Вы не сможете использовать synchronized в переменных или атрибутах в определении класса.

Синхронизация потоков, блокировка объекта и блокировка класса info.javarush.ru: http://goo.gl/gW4ONp


4. Как взаимодействуют программы, процессы и потоки?

Чаще всего одна программа состоит из одного процесса, но бывают и исключения (например, браузер Chrome создает отдельный процесс для каждой вкладки, что дает ему некоторые преимущества, вроде независимости вкладок друг от друга). В каждом процессе может быть создано множество потоков. Процессы разделены между собой (>программы), потоки в одном процессе могут взаимодействовать друг с другом (методы wait, notify, join и т.д.).

5. В каких случаях целесообразно создавать несколько потоков?

Многопоточные приложения применяются в случаях, когда можно разделить программу на несколько относительно независимых частей. В этом случае чтобы один код не ждал другой их помещают в различные потоки. В качестве примера можно привести программу с графическим интерфейсом — пока выполняются какие-либо длительные вычисления в одном потоке, интерфейс может быть доступен пользователю и не зависать, если он выполняется в другом потоке.

6. Что может произойти если два потока будут выполнять один и тот же код в программе?

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

7. Что вы знаете о главном потоке программы?

Маленькие программы на Java обычно состоят из одной нити, называемой «главной нитью» (main thread). Но программы побольше часто запускают дополнительные нити, их еще называют «дочерними нитями». Главная нить выполняет метод main и завершается. Аналогом такого метода main, для дочерних нитей служит метод run интерфейса Runnable. Много потоков — много методов main (run()).

8. Какие есть способы создания и запуска потоков?

Существует несколько способов создания и запуска потоков.

С помощью класса, реализующего Runnable

  • Создать объект класса Thread .
  • Создать объект класса, реализующего интерфейс Runnable
  • Вызвать у созданного объекта Thread метод start() (после этого запустится метод run() у переданного объекта, реализующего Runnable )
Илон Маск рекомендует:  Faq как программно создать ярлык

С помощью класса, расширяющего Thread

  • Создать объект класса ClassName extends Thread .
  • Переопределить run() в этом классе (смотрите пример ниже, где передается имя потока ‘Second’)

С помощью класса, реализующего java.util.concurrent.Callable

  • Создать объект класса, реализующего интерфейс Callable
  • Создать объект ExecutorService с указанием пула потоков.
  • Создать объект Future. Запуск происходит через метод submit() ; Сигнатура: Future submit(Callable task)

Потоки в Java. Наследуемся от Thread, реализуем интерфейс Runnable

Этой статьей я начинаю цикл статей по многопоточности и параллелизму в Java. Сегодня познакомимся с Thread и интерфейсом Runnable. Для того, чтобы понимать многопоточность в Java, нужно знать некоторые понятия.

Процесс в Java

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

Thread в Java

Thread можно назвать легковесным процесс. Он требует меньше ресурсов для создания и существует в процессе, деля с ним ресурсы.

Многопоточность в Java

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

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

Преимущества потоков

  1. Потоки намного легче процессов, они требуют меньше времени и ресурсов.
  2. Переключение контекста между потоками намного быстрее, чем между процессами.
  3. Намного проще добиться взаимодействия между потоками, чем между процессами.

Java предоставляет два способа программно создать поток.

  1. Реализация интерфейса java.lang.Runnable .
  2. Расширение класса java.lang.Thread .


Пример создания Thread. Реализуем интерфейс Runnable

Для того, чтобы класс был runnable, мы должны реализовать интерфейс java.lang.Runnable и обеспечить реализацию метода public void run() . Чтобы использовать этот класс, как поток, мы должны создать объект Thread, передавая объект runnable класса, а затем вызвать метод start() , чтобы выполнился метод r un() в отдельном потоке.

Вот пример Java класса, реализующего Runnable интерфейс.

Класс System.Threading.Thread

Основным в пространстве имен System.Threading является класс Thread. Этот класс представляет собой объектный контейнер отдельной ветви выполнения в конкретном домене приложения. Он определяет ряд методов (как статических, так и общедоступных), которые позволяют создавать новые потоки в текущем домене приложения, а также приостанавливать, останавливать и завершать отдельные потоки. Рассмотрите описания основных статических членов, приведенные в табл. 14.2.

Таблица 14.2. Основные статические члены типа Thread

Статический член Описание
CurrentContext Доступное только для чтения свойство, возвращающее контекст, в котором выполняется поток в настоящий момент
CurrentThread Доступное только для чтения свойство, возвращающее ссылку на выполняемый в настоящий момент поток
GetDomain() GetDomainID() Методы, возвращающие ссылки на текущий домен приложения или идентификатор домена, в котором выполняется текущий поток
Sleep() Метод, приостанавливающий выполнение текущего потока на указанное время

Класс Thread также поддерживает набор членов уровня экземпляра. Описания некоторых из этих членов приведены в табл. 14.3.

Таблица 14.3. Члены уровня экземпляра типа Thread

многопоточность — создание потока внутри класса и выполнение его на функции внутри класса в c ++ (cpp)

Я хочу сделать программу, где есть несколько потоков, единственное взаимодействие между потоками должно быть несколько переменных, созданных только для передачи между потоками. Одним из них будет очередь. Я хочу, чтобы один поток постоянно записывал в файл, и я хочу, чтобы другие потоки продолжали давать ему строки для записи.

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

Почему это не имеет смысла и почему это не работает, бьет меня. Он запускается, а затем он падает. Я не могу понять это. Это работает, когда у меня нет этого в классе, но тогда кто-то может связываться с потоком, и я действительно не хочу этого. Я не собираюсь выдавать слишком много кода из проекта, если в этом нет необходимости. Причина, по которой я создаю этот класс и другие функции и переменные, — это то, что вам не нужно знать.

Я понимаю, что мне нужен писатель. Join () где-то. Но это на потом. Я хочу, чтобы другая переменная могла завершить все сразу. Доступ к этой переменной возможен только из основного потока, поэтому другие потоки не могут связываться с ним. вроде как переменная kill_all.

Решение

Здесь есть несколько проблем, но, в первую очередь, это std::thread конструктор принимает функцию для выполнения в только что запущенном потоке. Похоже, вы не передаете функцию, вы передаете метод класса.

Я вполне уверен, что ваш компилятор кричал на вас, когда пытался скомпилировать этот код. Тот факт, что ваш компилятор решил создать какой-то скомпилированный объектный файл, несмотря на сообщенные им ошибки, не означает, что диагностику компилятора можно игнорировать.

Вы, вероятно, пытались сделать что-то вроде этого:

Теперь вы вызываете статическую функцию и передаете ей указатель на экземпляр класса, который он использует для вызова метода класса.

Но настоящая причина, по которой вы рухнули, заключается в том, что вы создаете экземпляр std::thread внутри сферы bar конструктор.

И когда bar конструктор возвращает, std::thread объект, как и любой другой объект в области функций / методов, уничтожается, вызывая его деструктор. Но вы только что запустили поток, который работает, и если std::thread деструктор вызывается во время работы потока, terminate() вызывается, прерывая программу.

Вы должны join() прежде чем уничтожить экземпляр std::thread , если поток работает. Итак, либо как то так:

Или сделать пример std::thread член класса, создание его экземпляра в конструкторе и присоединение к нему в деструкторе. Что-то вроде:

Конечно, правильный подход зависит от требований вашего приложения.

Другие решения

Простое изменение в вашем классе с указанием на @Sam Varchavchik — убедиться, что вы не потеряете объекты потока сразу после его создания.

Здесь поток конструктора инициализируется в конструкторе, и мы ожидаем его завершения в деструкторе.

Вы можете посмотреть, как работает слегка измененный код: http:// >

Как сказал Сэм, в старом C ++ нельзя было просто передать метод класса для создания потока, потому что для этого также необходим объект. Современный c ++ позволяет эту работу, связывая их вместе, как вы сделали, передавая this в конструктор потока.

Класс Thread

Поддержка многопоточности осуществляется в .NET в основном с помощью пространства имен System.Threading. Некоторые типы этого пространства описа­ны в табл. 10.1.

Таблица 10.1.Некоторые типы пространства имен System .Threading
Тип Описание

Interlocked Класс, обеспечивающий синхронизированный доступ к переменным, которые используются в разных потоках

Monitor Класс, обеспечивающий синхронизацию доступа к объектам

Mutex Класс-примитив синхронизации, который используется также для синхронизации между процессами
ReaderWriterLock Класс, определяющий блокировку, поддерживающую один доступ на запись и несколько — на чтение Thread Класс, который создает поток, устанавливает его приоритет, получает информацию о состоянии

ThreadPool Класс, используемый для управления набором взаимосвязанных потоков — пулом потоков

Timer Класс, определяющий механизм вызова заданного метода в заданные интервалы времени для пула потоков

WaitHandle Класс, инкапсулирующий объекты синхронизации, которые ожидают доступа к разделяемым ресурсам

IOCompletionCallback Класс, получающий сведения о завершившейся операции ввода-вывода

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

TimerCallback Делегат, представляющий метод, обрабатывающий вызовы от класса Timer

WaitCallback Делегат, представляющий метод для элементов класса ThreadPool

ThreadPriority Перечисление, описывающее приоритет потока

ThreadState Перечисление, описывающее состояние потока

Первичный поток создается автоматически. Для запуска вторичных потоков ис­пользуется класс Thread. При создании объекта-потока ему передается делегат, определяющий метод, выполнение которого выделяется в отдельный поток:

Thread t = new Thread ( new ThreadStart( имя_метода ) );

После создания потока заданный метод начинает в нем свою работу, а первич­ный поток продолжает выполняться. В листинге 10.10 приведен пример одно­временной работы двух потоков.

Листинг 10.10.Создание вторичного потока

static public void Hedgehog() // метод для вторичного потока

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL