Многопоточность в Java часть 1. Класс Thread
Чтобы понять сложность разработки многопоточных приложений, нужно окунуться в историю. И так, на заре компьютерной эры, когда были изобретены микропроцессоры, разработчики писали последовательный код. Не уверен, что в то время кто-то вообще мог думать о параллельных вычислениях. Последовательная модель интуитивно понятна, команды выполняются на одном процессоре одна за другой. Скорость выполнения программ оставляла желать лучшего и для ее улучшения был выбран путь – увеличение количества транзисторов на интегральной схеме одного процессора. Возможно вы слышали Закон Мура – основателя компании Intel. Он предсказал удвоение количества транзисторов каждые два года. Мур оказался прав, с каждым годом процессоры становились все быстрее и быстрее. Написанные последовательные программы сами по себе начинали работать быстрее, без изменений в коде! Представляете, вы не пишите код, а ваша программа начинает работать все быстрее и быстрее год за годом. Фантастика! Ближе к 2000-м годам стало понятно, что экспоненциальный рост количества транзисторов заканчивается и скоро упрется в физические ограничения материалов, из которых их и делают. Следующее архитектурное решение повлияло на судьбы многих языков программирования, людей, систем и, в частности, стало возможным написание этой статьи. В замен одноядерных систем стали появляться многоядерные, которые и открыли дорогу многопоточному, параллельному программированию. С этого момента, для ускорение программ и вычислений, стало необходимым задействовать все доступные процессоры, а это возможно только при делении задачи на отдельные части и их параллельное выполнение на разных процессорах. На первый план вышли языки, исторически заточенные под многоядерную и распределенную работу. Одним из мастодонтов стал Earlang. Другим языкам, в том числе Java, пришлось адаптироваться и превращаться из последовательного языка в параллельный. Возможно поэтому многопоточность в Java является очень тяжелой темой для понимания и изучения. Свой отпечаток на написание параллельных программ оставляет наш последовательный образ мышления людей как биологического вида. Люди – однопоточные. Сделаю небольшую ремарку. В эпоху одноядерных процессоров так же было возможно “параллельное” выполнение за счет работы потоков операционной системы. Каждый процесс имеет свое адресное пространство и представляет собой выполняемую программу. Таких процессов может быть много, и в одноядерной системе, действительно, создается иллюзия параллельного выполнения, но на самом деле, процессор осуществляет выполнения только одного процесса в единицу времени. Для выполнения другого процесса, процессор осуществляет прерывания текущего процесса и запускает следующий. В глазах пользователя это происходит настолько быстро, и кажется, что программа работает параллельно, но на самом деле – она последовательна.
Thread
Начиная с версии Java 1.0 в пакете java.lang есть специальный класс, который позволяет создавать новые потоки (или треды) – Thread. Этот класс реализует интерфейс Runnable – специальный интерфейс, который является функциональным, начиная с версии Java 8, и содержит один метод, в котором должна быть описана задача потока – то что он будет делать:
повторное использование потока Java через исполнителя
Я запутался в следующем:
Чтобы использовать потоки в программе Java, самый простой способ — расширить класс Thread и реализовать исполняемый интерфейс (или просто реализовать runnable).
Чтобы запустить выполнение потока. мы должны вызвать метод Thread(), который, в свою очередь, вызывает метод run() потока. И так начинается поток.
Метод start() (если я не ошибаюсь) нужно называть точно и только один раз для каждого потока. В результате экземпляры потоков не могут быть повторно использованы, если каким-то образом сам метод запуска не выполняется в некотором коротком бесконечном цикле, что облегчает пользовательскую реализацию повторного использования потока.
Теперь javadoc текст ссылки говорит
Я не понимаю, как это реализовано. Я предоставляю в методе выполнения метода исполнителя свой пользовательский поток, например.
ExecutorService myCachedPool = Executors.newCachedThreadPool(); myCachedPool.execute(new Runnable()>);
Как этот пользовательский поток, который я удаляю в структуру исполнителя, повторно используется?
Является ли Executor разрешено вызывать метод start() более одного раза, а мы не можем в наших программах? Я что-то не понимаю? Спасибо.
3 ответа
Обратите внимание, что это не Executor , который вызывает start() — it ExecutorService . И нет, он не называет start() дважды. Он не запускает задачу, которую вы даете ей напрямую, используя Thread.start() . вместо этого, она запускает поток, который знает об этой очереди пула потоков. Поток будет в основном ждать, пока не будет какая-то работа, а затем забрать его и выполнить, прежде чем вернуться к ожиданию. Поэтому, хотя поток выполняет несколько задач, Thread.start() вызывается только один раз. EDIT: судя по комментариям, вы немного смущены различием между Runnable (который является выполняемой задачей) и Thread (это то, что выполняет задачи). Тот же поток может выполнять несколько задач. Для очень простого примера, не использующего пул потоков, рассмотрим следующее:
public class MultiRunnable implements Runnable < private final Listrunnables; public MultiRunnable(List runnables) < this.runnables = runnables; >public void run() < for (Runnable runnable : runnables) < runnable.run(); >> >
(Игнорируйте потенциальные проблемы безопасности потока при использовании List
@Jon: Извини, что потерял тебя. То есть внутренние потоки фреймворка используются повторно, а не исполняемый файл, который я передаю в качестве аргумента для выполнения? Мой поток, который я делегирую фреймворку, будет каждый раз пересматриваться, но одним и тем же экземпляром внутреннего потока, созданного фреймворком?
@ user384706: Да. Ваш исполняемый файл не является потоком — это просто задача, которую нужно выполнить. Вы должны различать два; они очень разные.
@Jon: Большое спасибо. Единственное, в чем я не уверен, это то, что выгода от использования Executors.newCachedThreadPool (); Потому что, если мой класс, который реализует runnable (для задачи), является дорогостоящим для создания экземпляра, он не будет повторно использоваться, и тот же поток структуры будет продолжать использовать новые экземпляры задачи. Так что бы я получил от этого API? Если концепция не состоит в том, что каждый класс, который реализует runnable, является минимальным.
@ user384706 Создание потока имеет относительно большие издержки, и вы ничего не можете с этим поделать. Создание экземпляра класса очень дешево по сравнению (и если вы сделали его дорогим, например, он загружает базу данных / читает файлы и т. Д. В своем конструкторе, вы можете реорганизовать это, в отличие от потоков, накладные расходы которых вы не можете контролировать)
@nos: То есть мои задачи не должны расширять Thread, а просто выполнять работоспособный? Потому что, если действительно расширить поток, «относительно большие издержки» создания потока все еще там. Правильно?
Как запустить / остановить / перезапустить поток в Java?
Мне очень трудно найти способ запуска, остановки и перезапуска потока в Java. В частности, у меня есть класс Task (в настоящее время реализует Runnable ) в файле Task.java . Мое основное приложение должно иметь возможность ЗАПУСТИТЬ эту задачу в потоке, STOP (убить) поток, когда это необходимо, а иногда KILL и RESTART поток. Моя первая попытка заключалась в ExecutorService , но я не могу найти способ перезапустить задачу. Когда я использую .shutdownnow() , любой будущий вызов .execute() терпит неудачу, потому что ExecutorService — это «выключение». Итак, как я мог это сделать?
10 ответов
Как только поток остановится, его нельзя перезапустить. Однако нет ничего, что мешает вам создавать и запускать новый поток.
Вариант 1: Создайте новый поток, а не пытайтесь перезапустить.
Вариант 2: Вместо того, чтобы остановить поток, попросите его подождать, а затем, когда он получит уведомление, вы можете позволить ему снова выполнить работу. Таким образом, поток никогда не останавливается и никогда не будет перезагружен.
Изменить на основе комментария:
Чтобы «убить» поток, вы можете сделать что-то вроде следующего.
yourThread.setIsTerminating(true); // tell the thread to stop yourThread.join(); // wait for the thread to stop
К сожалению, я должен убить / перезапустить его . У меня нет полного контроля над содержимым потока, и для моей ситуации требуется перезапуск . Итак, создание нового — это хорошо . но как мне убить текущий первый?
Если вы действительно хотели «убить» поток, никогда не полагайтесь и не пытайтесь использовать какой-либо из методов API, которые, кажется, делают это, например kill () stop () destroy (). Вместо этого, как предложил Тейлор, посмотрите на флаг прерывания: while (! Thread.currentThread (). IsInterrupted ()) <. >Когда поток прерывается, очистите все функции в этом потоке и вернитесь из метода run и начать новую тему.
Я предполагаю, что в вашем потоке есть цикл, поэтому он может проверить флаг, чтобы увидеть, должен ли поток остановиться или продолжить выполнение.
нет такого метода setIsTerminating () в потоке в 1.6 или 1.7. Пожалуйста, уточните свой пост — программисты должны сами устанавливать и проверять флаги.
setIsTerminating (true) будет чем-то, что вы реализуете, что заставит thead остановиться. Смотрите мой комментарий чуть выше.
@TaylorLeese- TaylorLeese- Этот ответ очень полезен. Не могли бы вы предоставить больше информации о предложенном вами варианте 2. Похоже, что большая часть обсуждения была посвящена первому варианту. Не могли бы вы указать мне на некоторые кодовые базы или веб-сайты, где я могу найти, как заставить поток ждать и продолжать работать, когда он получает уведомление
@TaylorLeese Я также хотел бы увидеть некоторые подробности о Варианте 2, в частности, как заставить его приостановить или возобновить нажатие кнопки.
Невозможно завершить поток, если код, выполняющийся в этом потоке, не проверяет и не разрешает завершение.
Вы сказали: «К сожалению, я должен убить/перезапустить его. У меня нет полного контроля над содержимым потока, и для моей ситуации он требует перезагрузки»
Если содержимое потока не позволяет завершить его exectuion, вы не можете завершить этот поток.
В вашем сообщении вы сказали: «Моя первая попытка была с ExecutorService, но я не могу найти способ перезапустить задачу. Когда я использую .shutdownnow(). «
Если вы посмотрите на источник «shutdownnow», он просто запускает и прерывает текущие потоки. Это не остановит их выполнение, если код в этих потоках не проверяет, был ли он отключен и, если это так, останавливает выполнение. Поэтому shutdownnow, вероятно, не делает то, что вы думаете.
Позвольте мне проиллюстрировать, что я имею в виду, когда говорю, что содержимое потока должно разрешить конец этого потока:
myExecutor.execute(new Runnable() < public void run() < while (true) < System.out.println("running"); >> >); myExecutor.shutdownnow();
Этот поток будет продолжать работать вечно, даже если вызывается shutdownnow, потому что он никогда не проверяет, завершено ли оно или нет. Однако этот поток отключится:
myExecutor.execute(new Runnable() < public void run() < while (!Thread.interrupted()) < System.out.println("running"); >> >); myExecutor.shutdownnow();
Так как этот поток проверяет, было ли оно прервано/выключено/завершено.
Итак, если вам нужен поток, который вы можете отключить, вам нужно убедиться, что он проверяет, прервано ли оно. Если вам нужен поток, который вы можете «выключить» и «перезапустить», вы можете сделать runnable, который может принимать новые задачи, как было упомянуто ранее.
Почему вы не можете закрыть запущенный поток? Ну, я на самом деле солгал, вы можете назвать «yourThread.stop()», но почему это плохая идея? Этот поток может быть синхронизирован (или другой критический раздел, но мы ограничимся настройками, защищенными синхронным ключевым словом здесь) в разделе кода, когда вы его остановите. синхронизирующие блоки должны выполняться в их entirity и только одним потоком, прежде чем к нему будет обращаться какой-либо другой поток. Если вы остановите поток в середине блока синхронизации, защита, установленная блоком синхронизации, окажется недействительной, и ваша программа попадет в неизвестное состояние. Разработчики делают вещи в синхронизирующих блоках, чтобы синхронизировать вещи, если вы используете threadInstance.stop(), вы уничтожаете значение синхронизации, что разработчик этого кода пытался выполнить и как разработчик этого кода ожидал, что его синхронизированные блоки будут ведут себя.