<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="http://wiki.linuxformat.ru/wiki/skins/common/feed.css?303"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://wiki.linuxformat.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Vanuan</id>
		<title>Linuxformat - Вклад участника [ru]</title>
		<link rel="self" type="application/atom+xml" href="http://wiki.linuxformat.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Vanuan"/>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:Contributions/Vanuan"/>
		<updated>2026-05-13T02:11:04Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.19.20+dfsg-0+deb7u3</generator>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF87-88:Java</id>
		<title>LXF87-88:Java</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF87-88:Java"/>
				<updated>2009-05-13T15:39:23Z</updated>
		
		<summary type="html">&lt;p&gt;Vanuan: /* Потоки в Java */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Java}}&lt;br /&gt;
&lt;br /&gt;
=== Потоки в Java ===&lt;br /&gt;
''ЧАСТЬ 4: Завершая курс молодого Java-бойца, '''Антон Черноусов''' научит вас управлять потоками… Жаль, что не денежными.''&lt;br /&gt;
&lt;br /&gt;
C каждым днем появляются все более мощные процессоры, многоядерная архитектура которых стала основной темой ушедшего года, поэтому двухядерный процессор в ноутбуке уже никого не удивляет. С одной стороны — это обстоятельство приближает возможности простого пользователя к возможностям «по-настоящему» больших систем. С другой (и рекламные буклеты об этом обычно молчат) — для того, чтобы использовать весь потенциал современных компьютеров, приложение должно «уметь» просчитать задачу фактически на двух или более процессорах.&lt;br /&gt;
&lt;br /&gt;
Создание эффективных алгоритмов для работы на многопроцессорных станциях — это большая и сложная работа. Несмотря на это, для любого программиста актуальна задача организации взаимодействия с медленными ресурсами (например, чтения, записи или копирования файлов, работы с принтером, сетью), так как немногие пользователи смирятся с тем, что их любимая программа «замирает» в момент выполнения какой-либо операции.&lt;br /&gt;
&lt;br /&gt;
Во избежание описанных проблем программа должна использовать потоки или процессы. Под процессом понимается заявка на потребление всех видов ресурсов системы, кроме одного — процессорного времени, или иначе говоря, процесс — это запущенная на выполнение программа (такое определение дается в [[LXF87/88:Java#Литература|[1]]]). Поток рассматривается как самостоятельная активность внутри процесса, хотя существуют другие трактовки этого понятия, которые зависят от используемой операционной системы (см., например, [[LXF87/88:Java#Литература|[2]]]). Поток получил свое название по аналогии с потоком команд, поступающих в процессор; при выполнении потоки делят адресное пространство и выделенную память внутри одного процесса. Процессорное время распределяется между различными потоками операционной системой, точнее, одним из компонентов ее ядра — планировщиком. Более полно с понятием процессов и потоков и механизмов работы с ними с точки зрения операционной системы вы можете ознакомиться в книге [[LXF87/88:Java#Литература|[3]]].&lt;br /&gt;
&lt;br /&gt;
Давайте завершим наш экскурс в теорию и окунемся в реальность Java. Под процессом здесь принято понимать всеобъемлющий контекст выполнения, обеспечивающий высокий уровень изоляции охватываемых им данных от внешнего мира, а под потоком — более «легковесный» активный агент; в контексте одного процесса может функционировать целое множество потоков [[LXF87/88:Java#Литература|[4]]]. Планирование потоков в Java обеспечивается внутренними механизмами JVM.&lt;br /&gt;
&lt;br /&gt;
=== Поток, он же thread ===&lt;br /&gt;
В Java существует два способа работы с потоками: первый заключается в реализации интерфейса Runnable, второй связан с наследованием&lt;br /&gt;
класса Thread, который уже реализует данный интерфейс. В обоих случаях класс должен предоставлять реализацию метода run(). Ниже&lt;br /&gt;
приведен пример класса, реализующего поток через наследование класса Thread:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class FirstThread extends Thread {&lt;br /&gt;
  public void run(){&lt;br /&gt;
    for (int i = 1 ; i &amp;lt; 30; i++)&lt;br /&gt;
      System.out.println(&amp;quot;It is in thread &amp;quot;+ i);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Собственно, метод run() и должен содержать некоторый набор инструкций (разумеется, на языке Java), которые вы хотите выполнить&lt;br /&gt;
в отдельном потоке. Например, если вы реализуете функцию копирования файла (а пользователь, скажем, копирует ISO-образ объемом&lt;br /&gt;
600 Мб), желательно, чтобы эта операция выполнялась в отдельном потоке, запуск которого можно производить следующим образом:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class ConsoleToThread {&lt;br /&gt;
  public static void main(String[] args) {&lt;br /&gt;
    FirstThread thread = new FirstThread();&lt;br /&gt;
    thread.start();&lt;br /&gt;
    for (int i = 1; i &amp;lt; 20; i++) {&lt;br /&gt;
      System.out.println(&amp;quot;It is in main &amp;quot; + i);&lt;br /&gt;
    }&lt;br /&gt;
    try {&lt;br /&gt;
      thread.join();&lt;br /&gt;
    } &lt;br /&gt;
    catch (InterruptedException ex) {&lt;br /&gt;
      System.out.println(&amp;quot;Exception in stop thread&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
При выполнении метода main() класса ConsoleToThread создается объект-поток FirstThread, который запускается на выполнение методом start() [заметьте — метод run() никогда не вызывается явно, — прим.ред.]. Метод join() используется в случае, когда необходимо «дождаться» завершения потока. Завершение работы потока происходит при выходе из метода run(), как явном (например, посредством return), так и неявном (если внутри метода возникло и не было обработано какое-то исключение).&lt;br /&gt;
&lt;br /&gt;
Имейте в виду (это важно!): повторный запуск уже отработавшего потока приведет к исключению IllegalThreadStateException.&lt;br /&gt;
&lt;br /&gt;
=== Реализация потока через Runnable ===&lt;br /&gt;
Давайте теперь рассмотрим пример работы с потоками через интерфейс Runnable. Если, допустим, класс SameRunnable реализует интерфейс Runnable, то запустить поток на основе этого класса на выполнение можно следующим образом:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
Runnable run = new SameRunnable();&lt;br /&gt;
Thread thread = new Thread(run);&lt;br /&gt;
thread.start();&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
В следующем примере в методе main() класса ConsoleToThreadTwo создается массив threadArray, состоящий из объектов-потоков. При&lt;br /&gt;
этом используется конструктор класса Thread, принимающий два параметра: ссылку на объект, реализующий интерфейс Runnable и&lt;br /&gt;
имя потока:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
Thread thread = new Thread(Runnable, ThreadName);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Метод getName() объекта, реализующего поток, возвращает указанное при создании имя. Обратите внимание, что в нем используется&lt;br /&gt;
статический метод Thread.currentThread(), возвращающий ссылку на объект Thread, соответствующий выполняющемуся в текущий момент&lt;br /&gt;
потоку:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class SecondThread implements Runnable {&lt;br /&gt;
  public String getName() {&lt;br /&gt;
    return Thread.currentThread().getName();&lt;br /&gt;
  }&lt;br /&gt;
  public void run() {&lt;br /&gt;
    for (int i = 1; i &amp;lt; 100000; i++) {&lt;br /&gt;
      if ((i % 10000) == 0) {&lt;br /&gt;
        System.out.println(getName() + &amp;quot; counts &amp;quot; + i / 10000);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
public class ConsoleToThreadTwo {&lt;br /&gt;
  public static void main(String[] args) {&lt;br /&gt;
    Thread[] threadArray = new Thread[3];&lt;br /&gt;
      for (int i=0; i&amp;lt;threadArray.length; i++){&lt;br /&gt;
        threadArray[i] = new Thread(new SecondThread(), &amp;quot;Thread &amp;quot; + i);&lt;br /&gt;
      }&lt;br /&gt;
      for (int i=0; i&amp;lt;threadArray.length; i++){&lt;br /&gt;
        threadArray[i].start();&lt;br /&gt;
        System.out.println(threadArray[i].getName() + &amp;quot; started&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Проследив за выводом этой программы, можно заметить, что процессорное время распределяется между потоками практически равномерно, однако порядок их выполнения во многом случаен.&lt;br /&gt;
&lt;br /&gt;
=== Приоритеты потоков ===&lt;br /&gt;
Для управления величиной процессорного времени, выделяемого потоку, можно воспользоваться приоритетами. Установка приоритетов происходит с помощью метода Thread.setPriority(), узнать текущий приоритет позволяет метод getPriority(). В классе Thread определены&lt;br /&gt;
три константы:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
MIN_PRIORITY = 1&lt;br /&gt;
NORM_PRIORITY = 5&lt;br /&gt;
MAX_PRIORITY = 10&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Важно понимать, что значение приоритета потока предназначено для Java-машины и не соответствует реальным приоритетам потоков&lt;br /&gt;
в операционной системе.&lt;br /&gt;
&lt;br /&gt;
Давайте немного изменим код метода main() класса ConsoleToThreadTwo:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public static void main(String[] args) {&lt;br /&gt;
  Thread[] threadArray = new Thread[3];&lt;br /&gt;
  int pr = 0;&lt;br /&gt;
  for (int i=0; i&amp;lt;threadArray.length; i++){&lt;br /&gt;
    threadArray[i] = new Thread(new SecondThread(), &amp;quot;Thread &amp;quot; + i);&lt;br /&gt;
    if (pr == 10) &lt;br /&gt;
      pr = 0; &lt;br /&gt;
    threadArray[i].setPriority(Thread.MIN_PRIORITY + pr);&lt;br /&gt;
    pr++;&lt;br /&gt;
  }&lt;br /&gt;
  for (int i=0; i&amp;lt;threadArray.length; i++){&lt;br /&gt;
    threadArray[i].start();&lt;br /&gt;
    System.out.println(threadArray[i].getName() + &amp;quot; started&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Анализ результатов работы класса показывает, что потоки, получившие более высокий приоритет, выполняются чаще. Также, благодаря&lt;br /&gt;
условию на значение переменной pr, setPriority() никогда не будет передан приоритет, превышающий 10 (MAX_PRIORITY). Если бы это про-&lt;br /&gt;
изошло, система выбросила бы исключение IllegalArgumentException.&lt;br /&gt;
&lt;br /&gt;
=== Потоки-демоны ===&lt;br /&gt;
Сделаем еще одно важное замечание: программа будет выполняться до тех пор, пока выполняется хотя бы один запущенный в ней поток;&lt;br /&gt;
единственным исключением являются потоки-демоны.&lt;br /&gt;
&lt;br /&gt;
Что же это такое? Демон отличается от «простого смертного» потока вызовом метода setDeamon(true), который необходимо сделать до&lt;br /&gt;
начала работы. Например, так:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
FirstThread thread = new FirstThread();&lt;br /&gt;
thread.setDeamon(true);&lt;br /&gt;
thread.start();&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Узнать, является ли поток демоном, можно с помощью метода isDeamon(). Обычно потоки-демоны создаются для обслуживания&lt;br /&gt;
некритичных задач, так как при завершении работы программа не дожидается их остановки, а прерывает их самостоятельно.&lt;br /&gt;
&lt;br /&gt;
=== Где искать потоки? ===&lt;br /&gt;
В большинстве случаев бывает необходимо отслеживать ранее запущенные на выполнение потоки. Использование массива потоков,&lt;br /&gt;
как в предыдущем примере, не всегда оправданно. Для хранения и обработки потоков в Java существует класс ThreadGroup. Группа,&lt;br /&gt;
к которой принадлежит создаваемый поток, опять-таки передается конструктору Thread():&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
ThreadGroup tg = new ThreadGroup(&amp;quot;NameThreadGroup&amp;quot;);&lt;br /&gt;
Thread thread = new Thread(tg, new SecondThread(), &amp;quot;ThreadName&amp;quot;);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Если группа не указана явно, поток будет помещен в тот же ThreadGroup, что и его родитель.&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
ThreadGroup tg = new ThreadGroup(&amp;quot;NameThreadGroup&amp;quot;);&lt;br /&gt;
Thread thread = new Thread(tg, new SecondThread(), &amp;quot;ThreadName&amp;quot;);&lt;br /&gt;
Thread thread1 = new Thread(tg, new SecondThread(), &amp;quot;ThreadName2&amp;quot;);&lt;br /&gt;
Thread thread2 = new Thread(tg, new SecondThread(), &amp;quot;ThreadName3&amp;quot;);&lt;br /&gt;
System.out.println(&amp;quot;active thread in group &amp;quot; + tg.activeCount());&lt;br /&gt;
thread.start();&lt;br /&gt;
thread1.start();&lt;br /&gt;
System.out.println(&amp;quot;active thread in group &amp;quot; + tg.activeCount());&lt;br /&gt;
Thread[] threads = new Thread[tg.activeCount()];&lt;br /&gt;
int m = tg.enumerate(threads);&lt;br /&gt;
System.out.println(&amp;quot;taked threads from group : &amp;quot; + m);&lt;br /&gt;
for (int i = 0; i &amp;lt; threads.length; i++) {&lt;br /&gt;
  System.out.println(threads[i].getName());&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
В представленном выше примере в экземпляр класса ThreadGroup помещаются три потока, два из которых запускаются на выполнение.&lt;br /&gt;
Количество активных потоков в группе определяется с помощью метода activeCount(), а в результате выполнения метода enumerate() формируется перечень всех активных потоков.&lt;br /&gt;
&lt;br /&gt;
=== Управление потоками ===&lt;br /&gt;
При запуске потоков следует учитывать и то, что их иногда приходится останавливать, причем как штатно, так и экстренно. Для того, чтобы&lt;br /&gt;
приостановить работу потока изнутри, допустим, в тот момент, когда закончилась доступные для обработки данные, можно использовать&lt;br /&gt;
два метода: sleep() и wait().&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class ThirdThread extends Thread {&lt;br /&gt;
  public void run() {&lt;br /&gt;
    for (int i = 1; i &amp;lt; 110; i++) {&lt;br /&gt;
      if (i == 10) {&lt;br /&gt;
        try {&lt;br /&gt;
          sleep(10000);&lt;br /&gt;
        } &lt;br /&gt;
        catch (InterruptedException e) {&lt;br /&gt;
          System.out.println(&amp;quot;the thread was awaken there (just a moment ago)&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      if (i == 100) {&lt;br /&gt;
        try {&lt;br /&gt;
          synchronized (this) { wait();}&lt;br /&gt;
        }&lt;br /&gt;
        catch (InterruptedException e) {&lt;br /&gt;
          System.out.println(&amp;quot;the thread was awaken there (just a moment ago)again&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
      } &lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Методу sleep() передается переменная типа long, соответствующая количеству миллисекунд, в течении которых поток будет «спать». В&lt;br /&gt;
случае wait() поток ждет пробуждения снаружи. Применение методов sleep() и wait() требует обработки исключительной ситуации, которая возникают при пробуждении потока. В представленном ниже классе ConsoleToThreadThree потоки пробуждаются с помощью метода interrupt():&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class ConsoleToThreadThree {&lt;br /&gt;
  public static void main(String[] args) {&lt;br /&gt;
    ThirdThread thread = new ThirdThread();&lt;br /&gt;
    thread.start();&lt;br /&gt;
    for (int i = 1; i &amp;lt; 20; i++) {&lt;br /&gt;
      System.out.println(&amp;quot;It is in main &amp;quot; + i);&lt;br /&gt;
    }&lt;br /&gt;
    thread.interrupt();&lt;br /&gt;
    for (int i = 1; i &amp;lt; 20; i++) {&lt;br /&gt;
      System.out.println(&amp;quot;It is in main &amp;quot; + i);&lt;br /&gt;
    }&lt;br /&gt;
    thread.interrupt();&lt;br /&gt;
    try {&lt;br /&gt;
      thread.join();&lt;br /&gt;
    } &lt;br /&gt;
    catch (InterruptedException ex) {&lt;br /&gt;
      System.out.println(&amp;quot;Exception in stop thread&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Отметим, что вызов метода wait() без блока синхронизации (synchronized), о котором мы поговорим чуть ниже, приводит к исключению IllegalMonitorStateException. Возникшая ошибка свидетельствует об отсутствии монитора у объекта (понятие монитора и синхронизация&lt;br /&gt;
тесно связаны, о чем мы тоже поговорим ниже). Если для приостановления потока был применен метод wait(), то для «пробуждения» потока&lt;br /&gt;
можно воспользоваться методом notify() или notifyAll(). Первый пробуждает один случайно выбранный спящий поток, а второй пытается&lt;br /&gt;
пробудить их всех.&lt;br /&gt;
&lt;br /&gt;
Кроме рассмотренных выше методов, иногда бывает целесообразно использовать метод yield(), который приостанавливает работу текущего потока. Метод yield() не переводит поток в режим ожидания, как wait(), но предоставляет другим потокам возможность начать работать&lt;br /&gt;
раньше, чем допустила бы Java-машина [фактически, поток, вызвавший yield() добровольно отдает свой квант процессорного времени, -&lt;br /&gt;
прим. ред.]. Метод yield() статичный, так что прекратить с его помощью работу другого потока не получится.&lt;br /&gt;
&lt;br /&gt;
Методов остановки потоков тоже нет (ранее присутствовали методы stop(), resume(), suspend(), но сейчас они объявлены как «deprecated» -&lt;br /&gt;
то есть нерекомендованными к использованию). На сегодня в Java принят уведомительный стиль остановки потока с помощью пары методов:&lt;br /&gt;
уже известного нам interrupt(), применяемого снаружи, чтобы выставить флаг завершения и метода isInterrupted(), вызываемого изнутри&lt;br /&gt;
потока, чтобы узнать состояние флага, свидетельствующего о том, что «пора закругляться».&lt;br /&gt;
&lt;br /&gt;
=== Мониторы и синхронизация ===&lt;br /&gt;
Что такое «монитор», о котором говорилось выше? Нет, это не дисплей, это — объект, используемый как защелка, то есть в данный момент&lt;br /&gt;
времени владеть монитором может только один поток. В случае, если поток завладел монитором, говорят, что он «вошел» в монитор, а все&lt;br /&gt;
остальные потоки, пытающиеся это сделать, будут заморожены (часто говорят, что они «ждут» монитора) до тех пор, пока владелец монитора&lt;br /&gt;
его не освободит, то есть не покинет.&lt;br /&gt;
&lt;br /&gt;
Если перейти к реалиям Java, то объектов типа монитор в явном виде просто нет! С каждым объектом связан неявный монитор, и чтобы&lt;br /&gt;
завладеть им, необходимо вызвать метод или блок, помеченный ключевым словом synchronized. Как только поток входит в такой блок, он&lt;br /&gt;
завладевает монитором объекта, переданного synchronized в качестве параметра. Так происходит и в классе ThirdThread, однако, в момент&lt;br /&gt;
вызова метода wait(), монитор отпускается. Пример synchronized-метода представлен ниже:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public synchronized boolean sameCheck() {&lt;br /&gt;
  if (a) {&lt;br /&gt;
    a = false; return true;&lt;br /&gt;
  } &lt;br /&gt;
  else {&lt;br /&gt;
    a = true; &lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
В целом, синхронизация — это механизм, обеспечивающий монопольный доступ участка кода к некоторому объекту. Одним из первых&lt;br /&gt;
способов, предложенных для синхронизации работы потоков, были семафоры, концепцию которых описал Дейкстра [Dijkstra] в 1965 году&lt;br /&gt;
(часто говорят, что семафор — это классический синхронизированный примитив). Семафор используется для предоставления доступа к огра-&lt;br /&gt;
ниченному количеству ресурсов. Как правило, у семафора есть две операции: P — занять ресурс и V — освободить ресурс. Он может быть&lt;br /&gt;
реализован следующим образом:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class SimpleSemaphore {&lt;br /&gt;
  int counter;&lt;br /&gt;
  public SimpleSemaphore() {&lt;br /&gt;
    this.counter = 1;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  public synchronized void p() throws InterruptedException {&lt;br /&gt;
    while (counter == 0) {&lt;br /&gt;
      wait();&lt;br /&gt;
    }&lt;br /&gt;
    counter = counter + 1;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  public synchronized void v() throws InterruptedException {&lt;br /&gt;
    counter = counter - 1;&lt;br /&gt;
    notify();&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Можно, конечно, реализовывать семафоры самостоятельно, но проще воспользоваться специальной библиотекой java.util.concurrent.&lt;br /&gt;
Кроме семафоров, она включает в себя еще много чего интересного.&lt;br /&gt;
&lt;br /&gt;
Отметим, что программа, в принципе, может не использовать ни один из методов синхронизации, обходясь методами wait() и notify().&lt;br /&gt;
&lt;br /&gt;
=== Взаимные блокировки ===&lt;br /&gt;
На этом наш рассказ можно было бы и завершить, но чтобы у вас не сложилось впечатление, что в мире многопоточных приложений все&lt;br /&gt;
так радужно, мы поговорим о неприятных последствиях синхронизации. Как только количество потоков начинает стремительно расти и&lt;br /&gt;
возникает необходимость синхронизированного доступа к ограниченному кругу объектов в различной последовательности, будьте готовы к&lt;br /&gt;
ошибкам типа deadlock — взаимным блокировкам.&lt;br /&gt;
&lt;br /&gt;
Взаимная блокировка — это ошибка, которая лучше всего описывается простой формулой: «Поток A держит монитор a и хочет захватить&lt;br /&gt;
монитор b, а поток B держит монитор b и хочет захватить монитор a». В результате оба засыпают «мертвым сном».&lt;br /&gt;
&lt;br /&gt;
Ошибка очень противная и возникает обычно в нетривиальных алгоритмах. Лечится взаимная блокировка грамотным проектированием и профилактическими мерами, вроде следующей: всегда захватывайте мониторы в одном и том же порядке.&lt;br /&gt;
&lt;br /&gt;
Сегодня мы поговорили о двух способах создания потоков Java, разобрались с приоритетами, познакомились со средствами управления работой потоков и демонами, а также сделали небольшой обзор методов синхронизации. Для того, чтобы начать практическую работу с потоками, этого вполне достаточно. Желающим разобраться во всем этом глубже я рекомендую ознакомится с книгой «Concurrent Programming in Java: Design Principles and Patterns», автором которой является Дуг Ли [Doug Lea] — она считается одной из лучших по данной тематике.&lt;br /&gt;
&lt;br /&gt;
На этом мы заканчиваем обзор основ программирования на Java и в [[LXF89:Java_EE|следующий раз]] поговорим о серверных приложениях — приготовьтесь&lt;br /&gt;
к Java Enterprise Edition!&lt;br /&gt;
&lt;br /&gt;
=== Литература ===&lt;br /&gt;
* 1. П. Кью «Использование UNIX», ISBN 5-8275-0019-4&lt;br /&gt;
* 2. В. Г. Олифер, Н. А. Олифер «Сетевые операционные системы», ISBN 5-272-00120-6&lt;br /&gt;
* 3. Д. Бэкон, Т. Харрис «Операционные системы», ISBN 5-94723-969-8&lt;br /&gt;
* 4. М. Фаулер «Архитектура корпоративных программных приложений», ISBN 5-8459-0579-6&lt;/div&gt;</summary>
		<author><name>Vanuan</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF87-88:Java</id>
		<title>LXF87-88:Java</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF87-88:Java"/>
				<updated>2009-05-13T15:37:39Z</updated>
		
		<summary type="html">&lt;p&gt;Vanuan: /* Потоки в Java */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Java}}&lt;br /&gt;
&lt;br /&gt;
=== Потоки в Java ===&lt;br /&gt;
''ЧАСТЬ 4: Завершая курс молодого Java-бойца, '''Антон Черноусов''' научит вас управлять потоками… Жаль, что не денежными.''&lt;br /&gt;
&lt;br /&gt;
C каждым днем появляются все более мощные процессоры, многоядерная архитектура которых стала основной темой ушедшего года, поэтому двухядерный процессор в ноутбуке уже никого не удивляет. С одной стороны — это обстоятельство приближает возможности простого пользователя к возможностям «по-настоящему» больших систем. С другой (и рекламные буклеты об этом обычно молчат) — для того, чтобы использовать весь потенциал современных компьютеров, приложение должно «уметь» просчитать задачу фактически на двух или более процессорах.&lt;br /&gt;
&lt;br /&gt;
Создание эффективных алгоритмов для работы на многопроцессорных станциях — это большая и сложная работа. Несмотря на это, для любого программиста актуальна задача организации взаимодействия с медленными ресурсами (например, чтения, записи или копирования файлов, работы с принтером, сетью), так как немногие пользователи смирятся с тем, что их любимая программа «замирает» в момент выполнения какой-либо операции.&lt;br /&gt;
&lt;br /&gt;
Во избежание описанных проблем программа должна использовать потоки или процессы. Под процессом понимается заявка на потребле-&lt;br /&gt;
ние всех видов ресурсов системы, кроме одного — процессорного времени, или иначе говоря, процесс — это запущенная на выполнение программа (такое определение дается в [[LXF87/88:Java#Литература|[1]]]). Поток рассматривается как самостоятельная активность внутри процесса, хотя существуют другие трактовки этого понятия, которые зависят от используемой операционной системы (см., например, [[LXF87/88:Java#Литература|[2]]]). Поток получил свое название по аналогии с потоком команд, поступающих в процессор; при выполнении потоки делят адресное пространство и выделенную память внутри одного процесса. Процессорное время распределяется между различными потоками операционной системой, точнее, одним из компонентов ее ядра — планировщиком. Более полно с понятием процессов и потоков и механизмов работы с ними с точки зрения операционной системы вы можете ознакомиться в книге [[LXF87/88:Java#Литература|[3]]].&lt;br /&gt;
&lt;br /&gt;
Давайте завершим наш экскурс в теорию и окунемся в реальность Java. Под процессом здесь принято понимать всеобъемлющий контекст выполнения, обеспечивающий высокий уровень изоляции охватываемых им данных от внешнего мира, а под потоком — более «легковесный» активный агент; в контексте одного процесса может функционировать целое множество потоков [[LXF87/88:Java#Литература|[4]]]. Планирование потоков в Java обеспечивается внутренними механизмами JVM.&lt;br /&gt;
&lt;br /&gt;
=== Поток, он же thread ===&lt;br /&gt;
В Java существует два способа работы с потоками: первый заключается в реализации интерфейса Runnable, второй связан с наследованием&lt;br /&gt;
класса Thread, который уже реализует данный интерфейс. В обоих случаях класс должен предоставлять реализацию метода run(). Ниже&lt;br /&gt;
приведен пример класса, реализующего поток через наследование класса Thread:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class FirstThread extends Thread {&lt;br /&gt;
  public void run(){&lt;br /&gt;
    for (int i = 1 ; i &amp;lt; 30; i++)&lt;br /&gt;
      System.out.println(&amp;quot;It is in thread &amp;quot;+ i);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Собственно, метод run() и должен содержать некоторый набор инструкций (разумеется, на языке Java), которые вы хотите выполнить&lt;br /&gt;
в отдельном потоке. Например, если вы реализуете функцию копирования файла (а пользователь, скажем, копирует ISO-образ объемом&lt;br /&gt;
600 Мб), желательно, чтобы эта операция выполнялась в отдельном потоке, запуск которого можно производить следующим образом:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class ConsoleToThread {&lt;br /&gt;
  public static void main(String[] args) {&lt;br /&gt;
    FirstThread thread = new FirstThread();&lt;br /&gt;
    thread.start();&lt;br /&gt;
    for (int i = 1; i &amp;lt; 20; i++) {&lt;br /&gt;
      System.out.println(&amp;quot;It is in main &amp;quot; + i);&lt;br /&gt;
    }&lt;br /&gt;
    try {&lt;br /&gt;
      thread.join();&lt;br /&gt;
    } &lt;br /&gt;
    catch (InterruptedException ex) {&lt;br /&gt;
      System.out.println(&amp;quot;Exception in stop thread&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
При выполнении метода main() класса ConsoleToThread создается объект-поток FirstThread, который запускается на выполнение методом start() [заметьте — метод run() никогда не вызывается явно, — прим.ред.]. Метод join() используется в случае, когда необходимо «дождаться» завершения потока. Завершение работы потока происходит при выходе из метода run(), как явном (например, посредством return), так и неявном (если внутри метода возникло и не было обработано какое-то исключение).&lt;br /&gt;
&lt;br /&gt;
Имейте в виду (это важно!): повторный запуск уже отработавшего потока приведет к исключению IllegalThreadStateException.&lt;br /&gt;
&lt;br /&gt;
=== Реализация потока через Runnable ===&lt;br /&gt;
Давайте теперь рассмотрим пример работы с потоками через интерфейс Runnable. Если, допустим, класс SameRunnable реализует интерфейс Runnable, то запустить поток на основе этого класса на выполнение можно следующим образом:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
Runnable run = new SameRunnable();&lt;br /&gt;
Thread thread = new Thread(run);&lt;br /&gt;
thread.start();&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
В следующем примере в методе main() класса ConsoleToThreadTwo создается массив threadArray, состоящий из объектов-потоков. При&lt;br /&gt;
этом используется конструктор класса Thread, принимающий два параметра: ссылку на объект, реализующий интерфейс Runnable и&lt;br /&gt;
имя потока:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
Thread thread = new Thread(Runnable, ThreadName);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Метод getName() объекта, реализующего поток, возвращает указанное при создании имя. Обратите внимание, что в нем используется&lt;br /&gt;
статический метод Thread.currentThread(), возвращающий ссылку на объект Thread, соответствующий выполняющемуся в текущий момент&lt;br /&gt;
потоку:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class SecondThread implements Runnable {&lt;br /&gt;
  public String getName() {&lt;br /&gt;
    return Thread.currentThread().getName();&lt;br /&gt;
  }&lt;br /&gt;
  public void run() {&lt;br /&gt;
    for (int i = 1; i &amp;lt; 100000; i++) {&lt;br /&gt;
      if ((i % 10000) == 0) {&lt;br /&gt;
        System.out.println(getName() + &amp;quot; counts &amp;quot; + i / 10000);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
public class ConsoleToThreadTwo {&lt;br /&gt;
  public static void main(String[] args) {&lt;br /&gt;
    Thread[] threadArray = new Thread[3];&lt;br /&gt;
      for (int i=0; i&amp;lt;threadArray.length; i++){&lt;br /&gt;
        threadArray[i] = new Thread(new SecondThread(), &amp;quot;Thread &amp;quot; + i);&lt;br /&gt;
      }&lt;br /&gt;
      for (int i=0; i&amp;lt;threadArray.length; i++){&lt;br /&gt;
        threadArray[i].start();&lt;br /&gt;
        System.out.println(threadArray[i].getName() + &amp;quot; started&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Проследив за выводом этой программы, можно заметить, что процессорное время распределяется между потоками практически равномерно, однако порядок их выполнения во многом случаен.&lt;br /&gt;
&lt;br /&gt;
=== Приоритеты потоков ===&lt;br /&gt;
Для управления величиной процессорного времени, выделяемого потоку, можно воспользоваться приоритетами. Установка приоритетов происходит с помощью метода Thread.setPriority(), узнать текущий приоритет позволяет метод getPriority(). В классе Thread определены&lt;br /&gt;
три константы:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
MIN_PRIORITY = 1&lt;br /&gt;
NORM_PRIORITY = 5&lt;br /&gt;
MAX_PRIORITY = 10&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Важно понимать, что значение приоритета потока предназначено для Java-машины и не соответствует реальным приоритетам потоков&lt;br /&gt;
в операционной системе.&lt;br /&gt;
&lt;br /&gt;
Давайте немного изменим код метода main() класса ConsoleToThreadTwo:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public static void main(String[] args) {&lt;br /&gt;
  Thread[] threadArray = new Thread[3];&lt;br /&gt;
  int pr = 0;&lt;br /&gt;
  for (int i=0; i&amp;lt;threadArray.length; i++){&lt;br /&gt;
    threadArray[i] = new Thread(new SecondThread(), &amp;quot;Thread &amp;quot; + i);&lt;br /&gt;
    if (pr == 10) &lt;br /&gt;
      pr = 0; &lt;br /&gt;
    threadArray[i].setPriority(Thread.MIN_PRIORITY + pr);&lt;br /&gt;
    pr++;&lt;br /&gt;
  }&lt;br /&gt;
  for (int i=0; i&amp;lt;threadArray.length; i++){&lt;br /&gt;
    threadArray[i].start();&lt;br /&gt;
    System.out.println(threadArray[i].getName() + &amp;quot; started&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Анализ результатов работы класса показывает, что потоки, получившие более высокий приоритет, выполняются чаще. Также, благодаря&lt;br /&gt;
условию на значение переменной pr, setPriority() никогда не будет передан приоритет, превышающий 10 (MAX_PRIORITY). Если бы это про-&lt;br /&gt;
изошло, система выбросила бы исключение IllegalArgumentException.&lt;br /&gt;
&lt;br /&gt;
=== Потоки-демоны ===&lt;br /&gt;
Сделаем еще одно важное замечание: программа будет выполняться до тех пор, пока выполняется хотя бы один запущенный в ней поток;&lt;br /&gt;
единственным исключением являются потоки-демоны.&lt;br /&gt;
&lt;br /&gt;
Что же это такое? Демон отличается от «простого смертного» потока вызовом метода setDeamon(true), который необходимо сделать до&lt;br /&gt;
начала работы. Например, так:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
FirstThread thread = new FirstThread();&lt;br /&gt;
thread.setDeamon(true);&lt;br /&gt;
thread.start();&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Узнать, является ли поток демоном, можно с помощью метода isDeamon(). Обычно потоки-демоны создаются для обслуживания&lt;br /&gt;
некритичных задач, так как при завершении работы программа не дожидается их остановки, а прерывает их самостоятельно.&lt;br /&gt;
&lt;br /&gt;
=== Где искать потоки? ===&lt;br /&gt;
В большинстве случаев бывает необходимо отслеживать ранее запущенные на выполнение потоки. Использование массива потоков,&lt;br /&gt;
как в предыдущем примере, не всегда оправданно. Для хранения и обработки потоков в Java существует класс ThreadGroup. Группа,&lt;br /&gt;
к которой принадлежит создаваемый поток, опять-таки передается конструктору Thread():&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
ThreadGroup tg = new ThreadGroup(&amp;quot;NameThreadGroup&amp;quot;);&lt;br /&gt;
Thread thread = new Thread(tg, new SecondThread(), &amp;quot;ThreadName&amp;quot;);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Если группа не указана явно, поток будет помещен в тот же ThreadGroup, что и его родитель.&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
ThreadGroup tg = new ThreadGroup(&amp;quot;NameThreadGroup&amp;quot;);&lt;br /&gt;
Thread thread = new Thread(tg, new SecondThread(), &amp;quot;ThreadName&amp;quot;);&lt;br /&gt;
Thread thread1 = new Thread(tg, new SecondThread(), &amp;quot;ThreadName2&amp;quot;);&lt;br /&gt;
Thread thread2 = new Thread(tg, new SecondThread(), &amp;quot;ThreadName3&amp;quot;);&lt;br /&gt;
System.out.println(&amp;quot;active thread in group &amp;quot; + tg.activeCount());&lt;br /&gt;
thread.start();&lt;br /&gt;
thread1.start();&lt;br /&gt;
System.out.println(&amp;quot;active thread in group &amp;quot; + tg.activeCount());&lt;br /&gt;
Thread[] threads = new Thread[tg.activeCount()];&lt;br /&gt;
int m = tg.enumerate(threads);&lt;br /&gt;
System.out.println(&amp;quot;taked threads from group : &amp;quot; + m);&lt;br /&gt;
for (int i = 0; i &amp;lt; threads.length; i++) {&lt;br /&gt;
  System.out.println(threads[i].getName());&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
В представленном выше примере в экземпляр класса ThreadGroup помещаются три потока, два из которых запускаются на выполнение.&lt;br /&gt;
Количество активных потоков в группе определяется с помощью метода activeCount(), а в результате выполнения метода enumerate() формируется перечень всех активных потоков.&lt;br /&gt;
&lt;br /&gt;
=== Управление потоками ===&lt;br /&gt;
При запуске потоков следует учитывать и то, что их иногда приходится останавливать, причем как штатно, так и экстренно. Для того, чтобы&lt;br /&gt;
приостановить работу потока изнутри, допустим, в тот момент, когда закончилась доступные для обработки данные, можно использовать&lt;br /&gt;
два метода: sleep() и wait().&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class ThirdThread extends Thread {&lt;br /&gt;
  public void run() {&lt;br /&gt;
    for (int i = 1; i &amp;lt; 110; i++) {&lt;br /&gt;
      if (i == 10) {&lt;br /&gt;
        try {&lt;br /&gt;
          sleep(10000);&lt;br /&gt;
        } &lt;br /&gt;
        catch (InterruptedException e) {&lt;br /&gt;
          System.out.println(&amp;quot;the thread was awaken there (just a moment ago)&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      if (i == 100) {&lt;br /&gt;
        try {&lt;br /&gt;
          synchronized (this) { wait();}&lt;br /&gt;
        }&lt;br /&gt;
        catch (InterruptedException e) {&lt;br /&gt;
          System.out.println(&amp;quot;the thread was awaken there (just a moment ago)again&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
      } &lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Методу sleep() передается переменная типа long, соответствующая количеству миллисекунд, в течении которых поток будет «спать». В&lt;br /&gt;
случае wait() поток ждет пробуждения снаружи. Применение методов sleep() и wait() требует обработки исключительной ситуации, которая возникают при пробуждении потока. В представленном ниже классе ConsoleToThreadThree потоки пробуждаются с помощью метода interrupt():&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class ConsoleToThreadThree {&lt;br /&gt;
  public static void main(String[] args) {&lt;br /&gt;
    ThirdThread thread = new ThirdThread();&lt;br /&gt;
    thread.start();&lt;br /&gt;
    for (int i = 1; i &amp;lt; 20; i++) {&lt;br /&gt;
      System.out.println(&amp;quot;It is in main &amp;quot; + i);&lt;br /&gt;
    }&lt;br /&gt;
    thread.interrupt();&lt;br /&gt;
    for (int i = 1; i &amp;lt; 20; i++) {&lt;br /&gt;
      System.out.println(&amp;quot;It is in main &amp;quot; + i);&lt;br /&gt;
    }&lt;br /&gt;
    thread.interrupt();&lt;br /&gt;
    try {&lt;br /&gt;
      thread.join();&lt;br /&gt;
    } &lt;br /&gt;
    catch (InterruptedException ex) {&lt;br /&gt;
      System.out.println(&amp;quot;Exception in stop thread&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Отметим, что вызов метода wait() без блока синхронизации (synchronized), о котором мы поговорим чуть ниже, приводит к исключению IllegalMonitorStateException. Возникшая ошибка свидетельствует об отсутствии монитора у объекта (понятие монитора и синхронизация&lt;br /&gt;
тесно связаны, о чем мы тоже поговорим ниже). Если для приостановления потока был применен метод wait(), то для «пробуждения» потока&lt;br /&gt;
можно воспользоваться методом notify() или notifyAll(). Первый пробуждает один случайно выбранный спящий поток, а второй пытается&lt;br /&gt;
пробудить их всех.&lt;br /&gt;
&lt;br /&gt;
Кроме рассмотренных выше методов, иногда бывает целесообразно использовать метод yield(), который приостанавливает работу текущего потока. Метод yield() не переводит поток в режим ожидания, как wait(), но предоставляет другим потокам возможность начать работать&lt;br /&gt;
раньше, чем допустила бы Java-машина [фактически, поток, вызвавший yield() добровольно отдает свой квант процессорного времени, -&lt;br /&gt;
прим. ред.]. Метод yield() статичный, так что прекратить с его помощью работу другого потока не получится.&lt;br /&gt;
&lt;br /&gt;
Методов остановки потоков тоже нет (ранее присутствовали методы stop(), resume(), suspend(), но сейчас они объявлены как «deprecated» -&lt;br /&gt;
то есть нерекомендованными к использованию). На сегодня в Java принят уведомительный стиль остановки потока с помощью пары методов:&lt;br /&gt;
уже известного нам interrupt(), применяемого снаружи, чтобы выставить флаг завершения и метода isInterrupted(), вызываемого изнутри&lt;br /&gt;
потока, чтобы узнать состояние флага, свидетельствующего о том, что «пора закругляться».&lt;br /&gt;
&lt;br /&gt;
=== Мониторы и синхронизация ===&lt;br /&gt;
Что такое «монитор», о котором говорилось выше? Нет, это не дисплей, это — объект, используемый как защелка, то есть в данный момент&lt;br /&gt;
времени владеть монитором может только один поток. В случае, если поток завладел монитором, говорят, что он «вошел» в монитор, а все&lt;br /&gt;
остальные потоки, пытающиеся это сделать, будут заморожены (часто говорят, что они «ждут» монитора) до тех пор, пока владелец монитора&lt;br /&gt;
его не освободит, то есть не покинет.&lt;br /&gt;
&lt;br /&gt;
Если перейти к реалиям Java, то объектов типа монитор в явном виде просто нет! С каждым объектом связан неявный монитор, и чтобы&lt;br /&gt;
завладеть им, необходимо вызвать метод или блок, помеченный ключевым словом synchronized. Как только поток входит в такой блок, он&lt;br /&gt;
завладевает монитором объекта, переданного synchronized в качестве параметра. Так происходит и в классе ThirdThread, однако, в момент&lt;br /&gt;
вызова метода wait(), монитор отпускается. Пример synchronized-метода представлен ниже:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public synchronized boolean sameCheck() {&lt;br /&gt;
  if (a) {&lt;br /&gt;
    a = false; return true;&lt;br /&gt;
  } &lt;br /&gt;
  else {&lt;br /&gt;
    a = true; &lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
В целом, синхронизация — это механизм, обеспечивающий монопольный доступ участка кода к некоторому объекту. Одним из первых&lt;br /&gt;
способов, предложенных для синхронизации работы потоков, были семафоры, концепцию которых описал Дейкстра [Dijkstra] в 1965 году&lt;br /&gt;
(часто говорят, что семафор — это классический синхронизированный примитив). Семафор используется для предоставления доступа к огра-&lt;br /&gt;
ниченному количеству ресурсов. Как правило, у семафора есть две операции: P — занять ресурс и V — освободить ресурс. Он может быть&lt;br /&gt;
реализован следующим образом:&lt;br /&gt;
&amp;lt;source lang = &amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class SimpleSemaphore {&lt;br /&gt;
  int counter;&lt;br /&gt;
  public SimpleSemaphore() {&lt;br /&gt;
    this.counter = 1;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  public synchronized void p() throws InterruptedException {&lt;br /&gt;
    while (counter == 0) {&lt;br /&gt;
      wait();&lt;br /&gt;
    }&lt;br /&gt;
    counter = counter + 1;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  public synchronized void v() throws InterruptedException {&lt;br /&gt;
    counter = counter - 1;&lt;br /&gt;
    notify();&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Можно, конечно, реализовывать семафоры самостоятельно, но проще воспользоваться специальной библиотекой java.util.concurrent.&lt;br /&gt;
Кроме семафоров, она включает в себя еще много чего интересного.&lt;br /&gt;
&lt;br /&gt;
Отметим, что программа, в принципе, может не использовать ни один из методов синхронизации, обходясь методами wait() и notify().&lt;br /&gt;
&lt;br /&gt;
=== Взаимные блокировки ===&lt;br /&gt;
На этом наш рассказ можно было бы и завершить, но чтобы у вас не сложилось впечатление, что в мире многопоточных приложений все&lt;br /&gt;
так радужно, мы поговорим о неприятных последствиях синхронизации. Как только количество потоков начинает стремительно расти и&lt;br /&gt;
возникает необходимость синхронизированного доступа к ограниченному кругу объектов в различной последовательности, будьте готовы к&lt;br /&gt;
ошибкам типа deadlock — взаимным блокировкам.&lt;br /&gt;
&lt;br /&gt;
Взаимная блокировка — это ошибка, которая лучше всего описывается простой формулой: «Поток A держит монитор a и хочет захватить&lt;br /&gt;
монитор b, а поток B держит монитор b и хочет захватить монитор a». В результате оба засыпают «мертвым сном».&lt;br /&gt;
&lt;br /&gt;
Ошибка очень противная и возникает обычно в нетривиальных алгоритмах. Лечится взаимная блокировка грамотным проектированием и профилактическими мерами, вроде следующей: всегда захватывайте мониторы в одном и том же порядке.&lt;br /&gt;
&lt;br /&gt;
Сегодня мы поговорили о двух способах создания потоков Java, разобрались с приоритетами, познакомились со средствами управления работой потоков и демонами, а также сделали небольшой обзор методов синхронизации. Для того, чтобы начать практическую работу с потоками, этого вполне достаточно. Желающим разобраться во всем этом глубже я рекомендую ознакомится с книгой «Concurrent Programming in Java: Design Principles and Patterns», автором которой является Дуг Ли [Doug Lea] — она считается одной из лучших по данной тематике.&lt;br /&gt;
&lt;br /&gt;
На этом мы заканчиваем обзор основ программирования на Java и в [[LXF89:Java_EE|следующий раз]] поговорим о серверных приложениях — приготовьтесь&lt;br /&gt;
к Java Enterprise Edition!&lt;br /&gt;
&lt;br /&gt;
=== Литература ===&lt;br /&gt;
* 1. П. Кью «Использование UNIX», ISBN 5-8275-0019-4&lt;br /&gt;
* 2. В. Г. Олифер, Н. А. Олифер «Сетевые операционные системы», ISBN 5-272-00120-6&lt;br /&gt;
* 3. Д. Бэкон, Т. Харрис «Операционные системы», ISBN 5-94723-969-8&lt;br /&gt;
* 4. М. Фаулер «Архитектура корпоративных программных приложений», ISBN 5-8459-0579-6&lt;/div&gt;</summary>
		<author><name>Vanuan</name></author>	</entry>

	</feed>