Как приостановить выполнение программы java

Обновлено: 08.07.2024

Каждый поток ассоциирован с классом java . lang . Thread . Есть два основных способа использования объектов Thread в многопоточном программировании:

  • Прямое создание и управление потоками с помощью создания экземпляров класса Thread .
  • Абстрагирование от управления потоками и передача задач в executor.

Содержание

java.lang.Thread

Объявление и запуск потока

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

  • Предоставить экземпляр класса, реализующего интерфейс java . lang . Runnable . Этот класс имеет один метод run ( ) , который должен содержать код, который будет выполняться в отдельном потоке. Экземпляр класса java . lang . Runnable передаётся в конструктор класса Thread вот так:
  • Написать подкласс класса Thread . Класс Thread сам реализует интерфейс java . lang . Runnable , но его метод run ( ) ничего не делает. Приложение может унаследовать класс от Thread и переопределить метод run ( ) :

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

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

Приостанавливаем исполнение с помощью метода sleep

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

Есть два варианта метода sleep : первый принимает в качестве параметра количество миллисекунд, на которое нужно остановить текущий поток, второй дополнительно принимает второй параметр, в котором указывается количество наносекунд, на которые нужно дополнительно остановить поток.

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

Обратите внимание, что метод main объявляет, что он throws InterruptedException . Это исключение бросается методом sleep , если поток прерывается во время ожидания внутри sleep . Так как эта программа не объявила никаких других потоков, которые могут прерывать текущий, то ей вовсе не обязательно обрабатывать это исключение.

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

Прерывание (interrupt) — это сигнал для потока, что он должен прекратить делать то, что он делает сейчас, и делать что-то другое. Что должен делать поток в ответ на прерывание, решает программист, но обычно поток завершается.

Поток отправляет прерывание вызывая метод public void interrupt ( ) класса Thread . Для того чтобы механизм прерывания работал корректно, прерываемый поток должен поддерживать возможность прерывания своей работы.

Как поток должен поддерживать прерывание своей работы? Это зависит от того, что он сейчас делает. Если поток часто вызывает методы, которые могут бросить InterruptedException , то он просто вызывает return при перехвате подобного исключения. Пример:

Многие методы, которые бросают InterruptedException , например методы sleep , останавливают своё выполнение и возвращают управление в вызвавший их код при получении прерывания (interrupt).

Что если поток выполняется длительное время без вызова методов, которые бросают исключение InterruptedException ? Тогда он может периодически вызывать метод Thread . interrupted ( ) , который возвращает true , если получен сигнал о прерывании. Например:

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

Это позволяет располагать код обработки прерывания потока в одной клаузе catch .

Механизм прерывания реализован с помощью внутреннего флага, известного как статус прерывания (interrupt status). Вызов Thread . interrupt ( ) устанавливает этот флаг. Когда поток проверяет наличие прерывания вызовов Thread . interrupted ( ) , то флаг статуса прерывания сбрасывается. Нестатический метод isInterrupted ( ) , который используется одним потоком для проверки статуса прерывания другого потока, не меняет флаг статуса прерывания.

По соглашению любой метод, который прерывает свою выполнение бросая исключение InterruptedException , очищает флаг статуса прерывания, когда он бросает это исключение. Однако есть вероятность, что флаг статуса прерывания будет сразу же установлен ещё раз, если другой поток вызовет interrupt ( ) .

Соединение

Метод join позволяет одному потоку ждать завершения другого потока. Если t является экземпляром класса Thread , чей поток в данный момент продолжает выполняться, то

приведёт к приостановке выполнения текущего потока до тех пор, пока поток t не завершит свою работу. Метод join ( ) имеет варианты с параметрами:

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

Как и методы sleep , методы join отвечают на сигнал прерывания, останавливая процесс ожидания и бросая исключение InterruptedException .

Простой пример

Пример состоит из двух потоков. Первый поток является главным потоком приложения, который имеет каждая программа на Java. Главный поток создаёт новый поток и ждёт его завершения. Если второй поток выполняется слишком долго, то главный поток прерывает его.

threadMessage ( "Waiting for MessageLoop thread to finish" ) ; if ( ( ( System . currentTimeMillis ( ) - startTime ) > patience )

Синхронизация

Потоки общаются в основном разделяя свои поля и поля объектов между собой. Эта форма общения очень эффективна, но делает возможным два типа ошибок: вмешательство в поток (thread interference) и ошибки консистентности памяти (memory consistency errors). Для того чтобы предотвратить эти ошибки, нужно использовать синхронизацию потоков.

Однако синхронизация может привести к конкуренции потоков (thread contention), которая возникает, когда два или более потока пытаются получить доступ к одному и тому же ресурсу одновременно, что приводит к тому, что среда выполнения Java выполняет один или более этих потоков более медленно или даже приостанавливает их выполнение. Голодание (starvation) и активная блокировка (livelock) — это формы конкуренции потоков. Смотрите пункт «Живучесть (Liveness)».

Вмешательство в поток (thread interference)

Рассмотрим простой класс Counter:

Counter спроектирован так, что каждый вызов метода increment добавляет 1 к c , а каждый вызов decrement вычитает 1 из c . Однако если объект Counter используется несколькими потоками, то вмешательство в поток может помешать этому коду работать как ожидалось.

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

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

  1. Получить текущее значение c .
  2. Увеличить полученное значение на 1.
  3. Сохранить увеличенное значение в c .

Предположим, что поток A вызывает increment , и в то же самое время поток B вызывает decrement . Начальное значение c равно 0, их пересечённые действия могут породить следующую последовательность шагов:

  1. Поток A получает c .
  2. Поток B получает c .
  3. Поток A увеличивает полученное значение, в результате получает 1.
  4. Поток B уменьшает полученное значение, в результате получает -1.
  5. Поток A сохраняет результат 1 в c .
  6. Поток B сохраняет результат -1 в c .

Результат потока A потерян, он был перезаписан потоком B. Такое частичное перекрытие действий — это только одна из возможностей. В некоторых других ситуациях может оказаться, что результат потока B будет потерян, либо ошибок не будет совсем. Из-за этого ошибки вмешательства в поток трудно обнаруживать и исправлять.

Ошибки консистентности памяти (memory consistency errors)

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

Ключ к исключению ошибок консистентности памяти — это пониманию связи происходит-до (happens-before). Эта связь гарантирует, что данные, записанные в память одной инструкцией, видимы в другой. Рассмотрим следующий пример. Предположим, что поле типа int объявлено и инициализировано:

Поле counter используется совместно двумя потоками A и B. Предположим, что поток A увеличивает counter :

сразу же после этого поток B выводит в консоль значение counter :

Если бы обе инструкции были выполнены одним потоком, то можно было бы смело предположить, что в консоль выведется число 1. Но если две инструкции выполняются разными потоками, то может быть выведено 0, так как нет гарантии, что изменение counter потоком A будет видимо потоком B, до тех пор пока программист не обеспечит связь происходит-до (happens-before) между этими инструкциями.

Есть разные способы создания связи происходит-до (happens-before). Один из них — это синхронизация, она будет расписана в следующих пунктах.

Мы уже видели два действия, которые порождают связь происходит-до (happens-before):

  • Когда инструкция вызывает Thread . start , каждая инструкция, которая имеет связь происходит-до (happens-before) с этой инструкцией, также имеет связь происходит-до (happens-before) с каждой инструкцией, выполняемой новым потоком. Все последствия действий кода, который был выполнен до создания нового потока, видимы новым потоком.
  • Когда поток завершается и приводит Thread . join другого потока к возврату выполнения, то все инструкции, которые были выполнены завершённым потоком, имеют связь происходит-до (happens-before) со всеми инструкциями, которые следуют за успешным соединением потока. Все последствия действий кода в потоке теперь видимы потоком, который осуществил соединение.

Синхронизированные (synchronized) методы

Язык программирования Java предоставляет два базовых способа синхронизации: синхронизированные методы (synchronized methods) и синхронизированные инструкции (synchronized statements). Есть другие, более сложные, способы синхронизации, они будут рассмотрены в дальнейшем.

Чтобы сделать метод синхронизированным (synchronized), просто добавьте ключевое слово synchronized к его объявлению:

Если count является экземпляром класса SynchronizedCounter , то синхронизированные методы имеют два эффекта:

  • Во-первых, два вызова синхронизированных метода на одном и том же объекте не могут пересекаться. Когда один поток выполняет синхронизированный метод объекта, то другие потоки, которые вызывают синхронизированные методы того же самого объекта, блокируются (приостанавливают своё выполнение) до тех пор, пока первый поток не завершит работу с объектом.
  • Во-вторых, когда синхронизированный метод завершает своё выполнение, то он автоматически делает связь происходит-до (happens-before) со всеми последующими вызовами синхронизированных методов того же самого объекта. Это гарантирует, что изменения состояния объекта будут видимы для других потоков.

Заметьте, что конструкторы не могут быть синхронизированными. Использование ключевого слова synchronized для конструктора приведёт к ошибке компиляции. Синхронизированные конструкторы не имеют смысла, так как только один поток, который создаёт объект, должен иметь доступ к нему во время создания.

Предупреждение: Когда создаёте объект, который будет совместно использоваться разными потоками, то будьте очень осторожны, чтобы ссылка на объект не «утекла» раньше времени. Например, предположим, что вы хотите сделать список List , который содержит экземпляры каждого классы. Вы можете захотеть добавить следующую строку в ваш конструктор:

Но тогда другие потоки смогут использовать instances для получения доступа к объекту до того, как его создание будет завершено.

Синхронизированные методы — простая стратегия для предотвращения вмешательства в поток (thread interference) и ошибок консистентности памяти (memory consistency errors): Если объект видим более чем одному потоку, то все чтения и записи полей объекта должны происходить через синхронизированные методы. (Важное исключение: поля с модификатором final , которые не могут быть изменены после создания экземпляра объекта, могут безопасно читаться из несинхронизированных методов после создания конструктора) Эта стратегия эффективна, но может содержать проблемы с живучестью (liveness).

Внутренние блокировки и синхронизация

Синхронизация построена вокруг внутренней сущности, известной как внутренняя блокировка или блокировка монитора. Внутренняя блокировка играет роль в обоих аспектах синхронизации: обеспечивает эксклюзивный доступ к внутреннему состоянию объекта и обеспечивает связь происходит-до (happens-before).

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

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

Если вызывается статический синхронизированный метод, то поток получает внутреннюю блокировку объекта Class , связанного с этим классом. Таким образом доступ к статическим полям контролируется другой блокировкой, отличной от блокировки любого из экземпляров класса.

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


Здравствуйте! В этой статье я вкратце расскажу вам о процессах, потоках, и об основах многопоточного программирования на языке Java.
Наиболее очевидная область применения многопоточности – это программирование интерфейсов. Многопоточность незаменима тогда, когда необходимо, чтобы графический интерфейс продолжал отзываться на действия пользователя во время выполнения некоторой обработки информации. Например, поток, отвечающий за интерфейс, может ждать завершения другого потока, загружающего файл из интернета, и в это время выводить некоторую анимацию или обновлять прогресс-бар. Кроме того он может остановить поток загружающий файл, если была нажата кнопка «отмена».

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

Давайте начнем. Сначала о процессах.

Процессы

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

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


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

Потоки

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

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

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

Вот как это выглядит:


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

То, что инструкции параллельных потоков выполняются вперемешку, в некоторых случаях может привести к конфликтам доступа к данным. Проблемам взаимодействия потоков будет посвящена следующая статья, а пока о том, как запускаются потоки в Java…

Запуск потоков

Каждый процесс имеет хотя бы один выполняющийся поток. Тот поток, с которого начинается выполнение программы, называется главным. В языке Java, после создания процесса, выполнение главного потока начинается с метода main(). Затем, по мере необходимости, в заданных программистом местах, и при выполнении заданных им же условий, запускаются другие, побочные потоки.

В языке Java поток представляется в виде объекта-потомка класса Thread. Этот класс инкапсулирует стандартные механизмы работы с потоком.

Запустить новый поток можно двумя способами:

Способ 1

Создать объект класса Thread, передав ему в конструкторе нечто, реализующее интерфейс Runnable. Этот интерфейс содержит метод run(), который будет выполняться в новом потоке. Поток закончит выполнение, когда завершится его метод run().

Выглядит это так:

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

Способ 2

Создать потомка класса Thread и переопределить его метод run():

В приведённом выше примере в методе main() создается и запускается еще один поток. Важно отметить, что после вызова метода mSecondThread.start() главный поток продолжает своё выполнение, не дожидаясь пока порожденный им поток завершится. И те инструкции, которые идут после вызова метода start(), будут выполнены параллельно с инструкциями потока mSecondThread.

Для демонстрации параллельной работы потоков давайте рассмотрим программу, в которой два потока спорят на предмет философского вопроса «что было раньше, яйцо или курица?». Главный поток уверен, что первой была курица, о чем он и будет сообщать каждую секунду. Второй же поток раз в секунду будет опровергать своего оппонента. Всего спор продлится 5 секунд. Победит тот поток, который последним изречет свой ответ на этот, без сомнения, животрепещущий философский вопрос. В примере используются средства, о которых пока не было сказано (isAlive() sleep() и join()). К ним даны комментарии, а более подробно они будут разобраны дальше.

В приведенном примере два потока параллельно в течении 5 секунд выводят информацию на консоль. Точно предсказать, какой поток закончит высказываться последним, невозможно. Можно попытаться, и можно даже угадать, но есть большая вероятность того, что та же программа при следующем запуске будет иметь другого «победителя». Это происходит из-за так называемого «асинхронного выполнения кода». Асинхронность означает то, что нельзя утверждать, что какая-либо инструкция одного потока, выполнится раньше или позже инструкции другого. Или, другими словами, параллельные потоки независимы друг от друга, за исключением тех случаев, когда программист сам описывает зависимости между потоками с помощью предусмотренных для этого средств языка.

Теперь немного о завершении процессов…

Завершение процесса и демоны

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

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

Объявить поток демоном достаточно просто — нужно перед запуском потока вызвать его метод setDaemon(true) ;
Проверить, является ли поток демоном, можно вызвав его метод boolean isDaemon() ;

Завершение потоков

В Java существуют (существовали) средства для принудительного завершения потока. В частности метод Thread.stop() завершает поток незамедлительно после своего выполнения. Однако этот метод, а также Thread.suspend(), приостанавливающий поток, и Thread.resume(), продолжающий выполнение потока, были объявлены устаревшими и их использование отныне крайне нежелательно. Дело в том что поток может быть «убит» во время выполнения операции, обрыв которой на полуслове оставит некоторый объект в неправильном состоянии, что приведет к появлению трудноотлавливаемой и случайным образом возникающей ошибке.

Вместо принудительного завершения потока применяется схема, в которой каждый поток сам ответственен за своё завершение. Поток может остановиться либо тогда, когда он закончит выполнение метода run(), (main() — для главного потока) либо по сигналу из другого потока. Причем как реагировать на такой сигнал — дело, опять же, самого потока. Получив его, поток может выполнить некоторые операции и завершить выполнение, а может и вовсе его проигнорировать и продолжить выполняться. Описание реакции на сигнал завершения потока лежит на плечах программиста.

Java имеет встроенный механизм оповещения потока, который называется Interruption (прерывание, вмешательство), и скоро мы его рассмотрим, но сначала посмотрите на следующую программку:

Incremenator — поток, который каждую секунду прибавляет или вычитает единицу из значения статической переменной Program.mValue. Incremenator содержит два закрытых поля – mIsIncrement и mFinish. То, какое действие выполняется, определяется булевой переменной mIsIncrement — если оно равно true, то выполняется прибавление единицы, иначе — вычитание. А завершение потока происходит, когда значение mFinish становится равно true.

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

Метод yield()

Статический метод Thread.yield() заставляет процессор переключиться на обработку других потоков системы. Метод может быть полезным, например, когда поток ожидает наступления какого-либо события и необходимо чтобы проверка его наступления происходила как можно чаще. В этом случае можно поместить проверку события и метод Thread.yield() в цикл:

Метод join()

В Java предусмотрен механизм, позволяющий одному потоку ждать завершения выполнения другого. Для этого используется метод join(). Например, чтобы главный поток подождал завершения побочного потока myThready, необходимо выполнить инструкцию myThready.join() в главном потоке. Как только поток myThready завершится, метод join() вернет управление, и главный поток сможет продолжить выполнение.

Метод join() имеет перегруженную версию, которая получает в качестве параметра время ожидания. В этом случае join() возвращает управление либо когда завершится ожидаемый поток, либо когда закончится время ожидания. Подобно методу Thread.sleep() метод join может ждать в течение миллисекунд и наносекунд – аргументы те же.

С помощью задания времени ожидания потока можно, например, выполнять обновление анимированной картинки пока главный (или любой другой) поток ждёт завершения побочного потока, выполняющего ресурсоёмкие операции:

В этом примере поток brain (мозг) думает над чем-то, и предполагается, что это занимает у него длительное время. Главный поток ждет его четверть секунды и, в случае, если этого времени на раздумье не хватило, обновляет «индикатор раздумий» (некоторая анимированная картинка). В итоге, во время раздумий, пользователь наблюдает на экране индикатор мыслительного процесса, что дает ему знать, что электронные мозги чем то заняты.

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

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

Работать с приоритетами потока можно с помощью двух функций:

void setPriority(int priority) – устанавливает приоритет потока.
Возможные значения priority — MIN_PRIORITY, NORM_PRIORITY и MAX_PRIORITY.

int getPriority() – получает приоритет потока.

Некоторые полезные методы класса Thread

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

boolean isAlive() — возвращает true если myThready() выполняется и false если поток еще не был запущен или был завершен.

setName(String threadName) – Задает имя потока.
String getName() – Получает имя потока.
Имя потока – ассоциированная с ним строка, которая в некоторых случаях помогает понять, какой поток выполняет некоторое действие. Иногда это бывает полезным.

static Thread Thread.currentThread() — статический метод, возвращающий объект потока, в котором он был вызван.

long getId() – возвращает идентификатор потока. Идентификатор – уникальное число, присвоенное потоку.

Заключение

Отмечу, что в статье рассказано далеко не про все нюансы многопоточного программирования. И коду, приведенному в примерах, для полной корректности не хватает некоторых нюансов. В частности, в примерах не используется синхронизация. Синхронизация потоков — тема, не изучив которую, программировать правильные многопоточные приложения не получится. Почитать о ней вы можете, например, в книге «Java Concurrency in Practice» или здесь (всё на английском).

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


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

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

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

Где время — это длина паузы в миллисекундах ( 1/1000 часть секунды).

Исполнение такой команды приостановит вашу программу на время миллисекунд. Примеры:

Приостановит программу на 2 секунды.
Приостановит программу на полсекунды.
Приостановит программу на 1 час.

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

2. Правильный расчет пауз

Длину паузы считать легко. Если вам нужно, чтобы программа что-то делала раз в секунду, поставьте паузу — 1000 мс. Если 2 раза в секунду, поставьте паузу 500 мс (1000/2).

Если нужно выполнять что-то 15 раз в секунду, паузу делаем длинной 66 мс (1000/15). Вроде бы все очень просто:

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

Смотрите. Допустим, у вас есть какое-то действие, выполнение которого занимает 100 мс. Вы хотите, чтобы оно выполнялось 5 раз в секунду. Какую длину паузы вам нужно выбрать? Точно не 200 мс.

Чтобы действие выполнялось 5 раз в секунду, нужно чтобы время выполнения действия + пауза равнялись 200 мс. Тогда оно действительно будет выполняться 5 раз в секунду. В нашем случае действие выполняется 100 мс, значит на паузу остается еще 100 мс

То, что время выполнения действия гораздо выше нуля, хорошо понимают разработчики игр. Хотя и люди, играющие в игры, тоже.

Если у игры 20 FPS, это значит, что за одну секунду она успевает отрисовать на экране всего 20 кадров. 1000/20 — получаем 50 мс. Именно столько длится время отрисовки кадра игры .

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

Есть ли какое-то решение?

3 ответа

Я хочу приостановить выполнение программы c++ на 5 секунд. В android Handler.postDelayed есть необходимый функционал того, что я ищу. Есть ли что-нибудь подобное в c++?

Как я могу спать или приостановить выполнение программы на несколько секунд в C? Я ищу что-то вроде этого, что используется в Java: Thread.sleep(interval); Можно ли это сделать с помощью C ? Заранее спасибо.

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

Я предполагаю, что вы действительно имеете в виду, что меняете машину IP? Вы должны быть уверены, что потоки находятся в состоянии, в котором система IP может быть изменена, а также что система IP changer должна ждать, пока все потоки не остановятся для изменения IP.

Это можно сделать с помощью CountDownLatch, чтобы указать, что потоки должны приостановиться - каждый поток отсчитывает защелку. Когда все потоки достигнут защелки, программа обновления system IP может продолжить работу. Как только он выполнит свою работу, он возобновит потоки, установив защелку на null.

Обработка исключений опущена для ясности.

Попробуйте этот класс ReadWriteLock в java 1.5 api.

Похожие вопросы:

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

Хорошо, я довольно новичок в программировании java, но мне действительно нужно приостановить выполнение программы до тех пор, пока не произойдет какое-то событие: while(!isItHappened());.

Как приостановить выполнение некоторого потока. У меня есть нить т и две пуговицы, PAUSE и CONTINUE. На паузе мне нужно приостановить выполнение потока и продолжить выполнение потока с точки, где он.

Я хочу приостановить выполнение программы c++ на 5 секунд. В android Handler.postDelayed есть необходимый функционал того, что я ищу. Есть ли что-нибудь подобное в c++?

Как я могу спать или приостановить выполнение программы на несколько секунд в C? Я ищу что-то вроде этого, что используется в Java: Thread.sleep(interval); Можно ли это сделать с помощью C ? Заранее.

Я ищу возможность приостановить и возобновить выполнение Selenium. начните выполнение selenium 2. пауза на определенном шаге явно каким-то образом (commandline/in-line code/manually) продолжить.

Я пытаюсь приостановить свою программу и смотрю на использование Thread.Sleep(). Прочитав о проблемах, которые это вызывает, Я пытаюсь реализовать таймер Swing. Однако, похоже, что Swing таймера.

Существует ли какая-либо комбинация клавиш для приостановки и возобновления выполнения программы в командной строке? Поскольку у меня есть большая программа для запуска, она занимает 30 минут, чтобы.

Я пытаюсь понять, как я могу приостановить выполнение C-программы с GDB в любом месте . Если у меня есть цикл без точек останова, как я могу просто приостановить его, когда захочу? Я тоже новичок в.

Существует ли прямой способ с помощью инструмента ready-at-hand приостановить выполнение отслеживаемого процесса при вызове определенных системных вызовов с определенными параметрами? В частности, я.

Читайте также: