Определение потока, процесса

Выполнение любой программы на компьютере тесно связано с такими понятиями как процесс и поток. Необходимо разделять эти две концепции. Чем процесс отличается от потока.

Процесс имеет законченный private набор ресурсов, в частности свое собственное адресное пространство. Процесс часто ассоциируется с отдельной программой или приложением, хотя то, что пользователь видит как единое приложение может состоять из нескольких процессов. Осуществление связи между процессами большинство операционных систем используют так называемый Inter Process Communication, IPC который реализуется посредством механизмов, предоставляемых ядром ОС или процессом, использующим механизмы ОС и реализующим новые возможности IPC. Может осуществляться как на одном компьютере, так и между несколькими компьютерами сети посредством таких механизмов как pipes и sockets.

Поток или thread - часто называется как легковесный процесс, существует внутри процесса. Каждый процесс имеет как минимум один thread , threads делят между собой ресурсы процесса включая память и файлы. Это делает использование процесса более эффективно, но в тоже время более проблематичным.

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

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

Первый способ опишем на основе класса который реализует интерфейс Runnable и осуществляет обратный отсчет

public class Task1 implements Runnable{
	protected int countDown = 10; // Default
	private static int taskCount = 0;
	private final int id = taskCount ++;
	public Task1(){};
	public Task1(int countDown) {
		this.countDown = countDown;
	}
	public String status() {
		return "#" + id + "("+ (countDown > 0 ? countDown : "Finish") + "), ";
	}
	@Override
	public void run() {
		while (countDown-- > 0) {
			System.out.print(status());
		}
	}
	
	public static void main(String[] arg) {
		for (int idx = 0; idx < 10; idx++) {
			new Thread(new Task1()).start();
		}
	}
}

Та же самая задача осуществляемая с помощью класса расширяющего класс Thread:

public class Task2 extends Thread {

	protected int countDown = 10; // Default
	private static int taskCount = 0;
	private final int id = taskCount++;

	public Task2() {};
	public Task2(int countDown) {
		this.countDown = countDown;
	}

	public String status() {
		return "#" + id + "("+ (countDown > 0 ? countDown : "Finish") + "), ";
	}

	@Override
	public void run() {
		while (countDown-- > 0) {
			System.out.print(status());
		}
	}

	public static void main(String[] args) {
		for (int idx = 0; idx < 10; idx++) {
			new Task2().start();
		}
	}
}

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

#0(9), #0(8), #0(7), #0(6), #2(9), #2(8), #2(7), #2(6), #2(5), #2(4), #2(3), #2(2), #2(1), #2(Finish),
#1(9), #1(8), #1(7), #1(6), #0(5), #0(4), #1(5), #0(3), #1(4), #0(2), #1(3), #1(2), #1(1), #1(Finish),
#0(1), #0(Finish), #3(9), #3(8), #3(7), #3(6), #3(5), #3(4), #3(3), #3(2),
#3(1), #3(Finish), #4(9), #4(8), #4(7), #4(6), #4(5), #4(4),
#4(3), #4(2), #4(1), #4(Finish), #5(9), #5(8), #5(7),
#5(6), #5(5), #5(4), #5(3), #5(2), #5(1), #5(Finish), #6(9),
#6(8), #6(7), #6(6), #6(5), #6(4), #6(3), #6(2), #6(1), #6(Finish),
#7(9), #7(8), #7(7), #7(6), #7(5), #7(4), #7(3), #7(2),
#7(1), #7(Finish), #8(9), #8(8), #8(7), #8(6), #8(5), #8(4), #8(3),
#8(2), #8(1), #8(Finish), #9(9), #9(8), #9(7), #9(6),
#9(5), #9(4), #9(3), #9(2), #9(1), #9(Finish)

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

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

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

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

public class By5 { private int iNumber = 5; // synchronized public int next() { iNumber++; Thread.yield(); iNumber++; iNumber++; Thread.yield(); iNumber++; Thread.yield(); iNumber++; return iNumber; } }

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

public class MyTask extends Thread { protected int countDown = 10; // Default private static int taskCount = 10; private final int id = taskCount++; private final By5 by5; public MyTask(By5 arg) { this.by5 = arg; } private void checker(int arg) { if (arg % 5 != 0) { System.out.println("Thread# " + id + " Checked " + arg); } } @Override public void run() { for (int idx = 0; idx < 25; idx++) { int i1 = 0; i1 = by5.next(); checker(i1); } } public static void main(String[] arg) { By5 by5 = new By5(); for (int idx = 0; idx < 25; idx++) { new MyTask(by5).start(); } } } После прогона программы получаем большое количество некорректных данных. Происходит это потому что несколько потоков одновременно обращаются к поставщику чисел, при том что процессор попеременно переключает потоки, значение числа iNumber находится в промежуточном состоянии в момент переключения с потока на поток. Для разрешение этой ситуации один из способов это использование synchronized методов. Если какой либо поток зашел в метод помеченный этим словом, то до тех пор пока он не закончит вычисления с ним никакой другой поток не сможет завладеть этим методом. То есть поток блокирует ресурс для всех других потоков пока не закончит с ним работать. Как происходит блокировка? Каждый объект в Java связан с монитором, который поток может блокировать или разблокировать. Только один поток может удерживать блокировку на мониторе. Любые другие потоки, пытающиеся заблокировать этот монитор, останавливаются, до тех пор пока не смогут получить блокировку на этом мониторе. Поток t может заблокировать определенный монитор несколько раз; Каждая операция разблокировки отменяет одну операцию блокировки. Поток захватывает монитор объекта когда начинает работать с участком защищенного кода. До тех пор пока монитор объекта захвачен ни один другой поток не может работать с какими то ни было synchronized блоками объекта. Если другой объект должен работать с этим же объектом, то он вначале попытается захватить монитор, если же монитор захвачен, то поток встанет в очередь ожидая освобождения монитора. В примере выше если мы раскомментируем слово synchronized то программа благополучно закончит свою работу, и все полученные данные будут корректны. В случае если необходимо блокировать только часть метода применяется так называемый synchronized блок в котором описывается часть кода которую необходимо защитить. Выше рассмотренный класс с таким блоком будет выглядеть следующим образом: public class By5 { private int iNumber = 5; public int next() { synchronized (this) { iNumber++; Thread.yield(); iNumber++; iNumber++; Thread.yield(); iNumber++; Thread.yield(); iNumber++; return iNumber; } } } После слова synchronized указано this что означает что заблокированным будет именно этот объект класса. С помощью этой конструкции возможно блокировка любого другого класса. То есть класс by5 может быть не защищен сам по себе, но возможно синхронизировать его путем указания synchronized блока при обращении к методу этого класса, например таким образом: В основном этот подход используется для создания оболочек вокруг класса которые позволяют использовать класс в многопоточном программировании, например в классе Collection method synchronizedList позволяет обернуть объект ArrayList thread-safe оболочкой для использования в многопоточной среде. @Override public void run() { for(int idx=0;idx<25;idx++){ int i1=0; synchronized(by5){ i1=by5.next(); } checker(i1); } } Библиотека Java SE5 java.util.concurrent также содержит явный механизм управления блокировкой. Объект lock можно явно создать в программе, установить или снять блокировку, полученный код будет более длинным, но также более гибким, класс by5 с механизмом lock будет выглядеть следующим образом: public class By5 { private int iNumber = 5; private Lock lock = new ReentrantLock(); public int next() { lock.lock(); try { iNumber++; Thread.yield(); iNumber++; iNumber++; Thread.yield(); iNumber++; Thread.yield(); iNumber++; return iNumber; } finally { lock.unlock(); } } } Для создания критической секции используются методы lock и unlock. Конструкцию try-finally следует применять, чтобы гарантировать снятие блокировки. В общем конструкция synchronized требует меньше кода, но при ошибках происходит исключение и невозможно выполнить завершающие действия. Также при таком подходе невозможно применить блокировку на заданное время с последующим отказом, а при использовании конструкции lock возможно следующее действие: lock.tryLock(100, TimeUnit SECONDS); то есть это попытка получить блокировку в течении заданного промежутка времени с последующим отказом. Еще один способ для разрешения ситуации с вышеприведенным кодом это использование Atomiс классов в частности AtomicInteger. В соответствии со спецификацией этот класс позволяет обновить значение integer атомарно. AtomicInteger используется ,например, в таких приложениях, как атомарно увеличивающиеся счетчики. В примере рассмотренном выше вместо синхронизации можно использовать метод этого класса addAndGet(int arg) который увеличивает значение переменной гарантированно без прерываний другими потоками. Язык программирования Java предоставляет еще один механизм синхронизации - volatile fields, более удобные, чем блокировка для некоторых целей. Переменная может быть объявлена volatile, и в этом случае Java Memory Model гарантирует, что все потоки будут видеть согласованное, или, другими словами одинаковое значение этой переменной. Например: class Test { static volatile int i = 0, j = 0; static void one() { i++; j++; } static void two() { System.out.println("i=" + i + " j=" + j); } } Это позволяет одновременно выполнять методы, но гарантирует, что доступ к общим значениям для i и j происходит ровно столько раз и в точно таком же порядке, в котором они появляются при выполнении текста программы каждым потоком. Поэтому значение для j никогда не будет больше, чем для i, потому что каждое изменение i будет обновлено перед тем, как произойдет обновление j. private volatile int iNumber = 5; Рассказать что такое volatile. И как применять и зачем нужно. И когда можно применять а когда нельзя. Рассказать что такое атомарные операции, и с какими типами данных они работают и почему. Sleep, Wait, Notify and etc Рассмотрим проблему, когда один поток создает некоторые данные, а другой принимает их. Предположим, что поставщик данных должен ожидать, когда потребитель завершит работу, прежде чем поставщик создаст новые данные. В системах с опросом тратится много циклов процессора на ожидание данных от поставщика. Как только поставщик завершает работу, он должен начать опрос, проверяющий забран ли товар потребителем для того чтоб начать производство следующей единицы, что расходует циклы про­цессора в ожидании завершения работы потребителя данных, и т.д. Понятно, что такая ситуация нежелательна. Чтобы избежать опроса, есть механизм межпроцессных коммуникаций с использованием методов wait (), notify () и notifyAll (). Эти методы находятся в классе Object, поэтому они доступны всем классам. Все три метода могут быть вызваны только из синхронизированного контекста. • Метод wait () принуждает вызывающий поток приоста­новить выполнение и отдать монитор до тех пор(именно в этом порядке), пока какой-нибудь другой поток не войдет в тот же монитор и не вызовет метод notify (). Существуют дополнительные формы метода wait (), позволяющие указать время ожидания. • Метод notify () возобновляет работу потока, который вызвал метод wait () в том же объекте. • Метод notifyAll () возобновляет работу всех потоков, которые вызвали метод wait () в том же объекте. Одному из потоков дается доступ. Java не определяет какому именно потоку будет отдан ресурс, то есть это может быть любой поток Ниже пример показывает работу wait и notify class Q { int n; boolean valueSet = false; synchronized int get() { while (!valueSet) { try { wait(); } catch (InterruptedException e) { System.out.println("InterruptedException"); } } System.out.println("Have taken: " + n); valueSet = false; notify(); return n; } synchronized void put(int n) { while (valueSet) { try { wait(); } catch (InterruptedException e) { System.out.println("InterruptedException"); } } this.n = n; valueSet = true; System.out.println("Have sent: " + n); notify(); } } class Producer implements Runnable { Q q; Producer(Q q) { this.q = q; new Thread(this, "Deliver").start(); } public void run() { int i = 0; while (true) { q.put(i++); } } } class Consumer implements Runnable { Q q; Consumer(Q q) { this.q = q; new Thread(this, "Consumer").start(); } public void run() { while (true) { q.get(); } } } public class PC { public static void main(String args[]) { Q q = new Q(); new Producer(q); new Consumer(q); } } Внутри метода get () вызывается метод wait (). Это приостанавливает работу потока до тех пор, пока объект класса Producer не известит вас о том, что данные прочитаны. Когда это происходит, выполнение внутри метода get () продолжает­ся. Метод sleep принадлежащий классу thread приостанавливает работу потока. В отличие от метода wait метод sleep не отпускает ресурсы оставляя их заблокированными. Thread.sleep заставляет текущий поток приостановить выполнение в течение определенного периода. Это является эффективным средством обеспечения других потоков процессорным временем. Предусмотрены два перегруженные версии sleep: первый определяет время ожидания до миллисекунды и второй определяет время sleep в наносекундах. Точность задания временных промежутков не гарантируется, и ограничиваются возможностями базовой ОС. Кроме того, период sleep может быть прерван методом interrupt. В любом случае, нет гарантии, что поток будет приостановлен на точно заданный период времени. Join Этот метод приостановит выполнение текущего потока до тех пор, пока другой поток не закончит свое выполнение. Если поток прерывается, бросается InterruptedException. Метод позволяет присоединиться к одному потоку ждать завершения другого. Если t является объектом класса Thread поток которого выполняется в данный момент, то t.join (); вызовет остановку текущего потока до тех пор пока поток t не закончит свою работу. Методы sleep и join отреагируют на метод interrupt(); выбросом исключения InterruptedException. Пример ниже демонстрирует работу join(); public class JoinExample { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Runnable() { public void run() { System.out.println("First task started"); System.out.println("Sleeping for 2 seconds"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("First task completed"); } }); Thread t1 = new Thread(new Runnable() { public void run() { System.out.println("Second task completed"); } }); t.start(); t.join(); t1.start(); } } Методы wait(), notify(), notifyAll() являются методами класса Object, так как они управляет блокировками, которые являются частью каждого объекта Почему sleep() и join() принадлежат классу Thread а не Object? Жизненный цикл потока Поток может находиться в нескольких раз­ных состояниях. Можно получить текущее состояние потока, вызвав метод getState (). Метод возвращает значение типа Thread. State, показывающее состояние потока на момент вызова. Значения Состояние BLOCKED Поток заблокирован в ожидание блокировки монитора или поток в заблокированном состоянии ожидает monitor lock чтобы войти в синхронизированный блок/метод или войти повторно в синхронизированный блок/метод после вызова Object.wait. NEW Поток еще не начал выполнение RUNNABLE Поток в настоящее время выполняется или начнет выполняться, ког­да получит доступ к процессору TERMINATED Поток закончил выполнение TIMED_WAITING Поток приостановил выполнение на определенный промежуток времени, например, после вызова метода Thread.sleep(sleeptime) Object.wait(timeout) Thread.join(timeout) WAITING Поток приостановил выполнение, поскольку он ожидает некое дей­ствие посредством вызовов методо Например, вызова Object.wait() Thread.join() LockSupport.park() Поток имеет состояние NEW после создания. Вызовом метода start() поток переходит в состояние RUNNABLE в котором он получит доступ к процессору или уже выполняется. Из состояния RUNNABLE поток переходит в состояние WAITING вызовом методов join(), wait() или методов ввода/вывода, которые предполагают задержку. Для задержки потока на некоторое время (в миллисекундах) можно перевести его в режим ожидания по времени TIMED_WAITING) с помощью методов sleep(longmillis), join(long timeout) и wait(long timeout), при выполнении которых может генерироваться прерывание InterruptedException. Вернуть потоку работоспособность после вызова метода wait() — методами notify()или notifyAll(). Поток переходит в состояние (TERMINATED), если метод run() завершил выполнение, и запустить его повторно уже невозможно. После этого, чтобы запустить поток, необходимо создать новый объект потока. Если же поток неработоспособен, например, находится в состоянии TIMED_WAITING, то метод INTERRUPT инициирует исключение InterruptedException. Чтобы это не происходило, следует предварительно вызвать метод isInterrupted(), который проверит возможность завершения работы потока. Планировщик — часть операционной системы, которая отвечает за (псевдо)параллельное выполнения задач, потоков, процессов. Планировщик выделяет потокам процессорное время, память, стек и прочие ресурсы. Планировщик может принудительно забирать управление у потока (например по таймеру или при появлении потока с большим приоритетом), либо просто ожидать пока поток сам явно(вызовом некой системной процедуры) или неявно(по завершении) отдаст управление планировщику. -- планировщик должен быть рассказан в жизненном цикле потока, соответственно нужно перенести приоритеты куда-нибудь вверх. Схема на рисунке демонстрирует взаимоотношения между различными состо­яниями потока. Приоритеты потоков Приоритеты потоков используются планировщиком потоков для принятия решений о том, когда каждому из потоков будет разрешено работать. Теоретически высокоприоритетные потоки получают больше времени процессора, чем низкоприоритетные. Высокоприоритетный поток может также выгружать низкоприоритетный. Потоки с равным приоритетом должны получать равный доступ к центральному процессору. JVM гарантирует, что все потоки независимо от приоритета получат время процессора. Чтобы установить приоритет потока, используется метод setPriority () класса Thread. Так выглядит его общая форма: final void setPriority(int arg); Значение уровень должно быть в пределах диапазона от MIN_PRIORITY до MAX_PRIORITY. Эти значения равны соответственно 1 и 10. Приоритет по умолчанию, NORM_PRlORITY равен 5. Эти приоритеты определены как статические финальные (static final) переменные в классе Thread. Получить текущее значение приоритета потока, вызвав метод getPriority () класса Thread. Рекомендации по использованию приоритетов. К сожалению не нашел в интернете никаких рекомендаций Поискать еще. Используя англоязычные ресурсы, форумы и др. Группа потоков представляет собой класс как набор потоков объединенных в одну группу. Группы потоков формирует дерево в котором каждый поток исключая исходный имеет предка. Существует два метода для создания класса группы потоков, это ThreadGroup(String name) и ThreadGroup(ThreadGroup parent, String name). Группы потоков удобно использовать, когда необходимо одинаково управлять несколькими потоками. Например, есть потоки, которые выводят данные на печать, и необходимо прервать печать всех документов поставленных в очередь. В этом случае удобно применить команду к группе потока, а не к каждому потоку отдельно. Но это можно сделать, если потоки отнесены к одной группе. Класс ThreadGroup определяет 2 метода для работы с группами это getMaxPriority() и setMaxPriority(int pri). getMaxPriority() - Возвращает максимальный приоритет этой группы потоков. Потоки, которые являются членами этой группы не могут иметь более высокий приоритет чем максимальный setMaxPriority(int pri) - Устанавливает максимальный приоритет группы, потоки в группе, которые уже имеют высокий приоритет, не затрагиваются. Если аргумент pri меньше Thread.MIN_PRIORITY или больше Thread.MAX_PRIORITY, максимальный приоритет группы остается неизменной. В противном случае, приоритет это группы устанавливается равным указанному аргументом, но не большему чем приоритет родителя этой группы. Потоками-демонами называются потоки, работающие в фоновом режиме для данной программы. В Java процесс завершается тогда, когда завершается последний его поток. Даже если метод main() уже завершился, но еще выполняются порожденные им потоки, система будет ждать их завершения. Однако это правило не относится к особому виду потоков – демонам. Если завершился последний обычный поток процесса, и остались только потоки-демоны, то они будут принудительно завершены и выполнение процесса закончится. Чаще всего потоки-демоны используются для выполнения фоновых задач, обслуживающих процесс в течение его жизни. Использовать такие потоки можно например для логирования действий пользователя или ведения журнала событий программы. Для создания потока демона нужно перед запуском потока вызвать его метод setDaemon(true); Проверить, является ли поток демоном, можно вызвав его метод boolean isDaemon(); С demon thread можно использовать все методы класса Thread в частности join(), sleep(), и т.д. Следует иметь в виду, что при использовании метода join()в случае когда демон ждет окончания работы основного потока лишено смысла поскольку после завершения главного потока демон завершится автоматически. Примеры потоков демонов в java Борьба за ресурс: dead lock, race condition Состояние гонки - причина трудно уловимых багов. Состояние гонки возникает из-за гонки между несколькими потоками, если поток, который должен исполнятся первым, проиграл гонку и исполняется второй, поведение кода изменяется, из-за чего возникают недетерминированные баги. Это одни из сложнейших к отлавливанию и воспроизведению багов, из-за беспорядочной природы гонок между нитями. Состояние гонки — «плавающая» ошибка (гейзенбаг), проявляющаяся в случайные моменты времени и «пропадающая» при попытке её локализовать. Переменная x объявлена как volatile для того чтоб ее изменение было видно всем потокам. Согласно спецификации Java имеется только одна копия переменной объявленной как volatile и какой бы поток не изменил ее, новое значение этой переменной будет доступно для всех потоков. При работе с volatile переменными необходимо знать, что чтение volatile-переменной и запись в volatile-переменную синхронизированы, а неатомарные операции ― нет. volatile int x; // Поток 1: while (!stop) { x++; … } // Поток 2: while (!stop) { if (x%2 == 0) System.out.println("x=" + x); … } Оператор if в потоке 2 проверяет x на чётность. Оператор «x++» в потоке 1 увеличивает x на единицу. Оператор вывода в потоке 2 выводит «x=1», хотя, казалось бы, переменная проверена на чётность. метод решения — синхронизация потоков: int x; // Поток 1: while (!stop) { synchronized(SomeObject) { x++; } … } // Поток 2: while (!stop) { synchronized(SomeObject) { if (x%2 == 0) System.out.println("x=" + x); } … } или использование AtomicInteger класса в частности метод addAndGet(int delta) произведет сложение чисел как атомарную операцию. В этом случае код принимает следующий вид AtomicInteger x = new AtomicInteger(0); // Поток 1: while (!stop){ ai.addAndGet(2); … } // Поток 2: while (!stop) { if (x%2 == 0) System.out.println("x=" + x); … } Взаимная блокировка (deadlock),происходит, когда по­токи имеют циклическую зависимость от пары синхронизированных объектов. Предположим, что один поток входит в монитор объекта X, а другой — в монитор объекта Y. Если поток в объекте X попытается вызвать любой синхронизирован­ный метод объекта Y, он будет блокирован, как и ожидалось. Однако если поток объекта Y, в свою очередь, попытается вызвать любой синхронизированный ме­тод объекта X, то поток будет ожидать вечно, поскольку для получения доступа к объекту X он должен снять свой собственный блок с объекта Y, чтобы первый поток мог работать. Взаимная блокировка является ошибкой, которую трудно от­ладить, по двум следующим причинам: • В общем, она случается довольно редко, когда выполнение двух потоков точ­но совпадает по времени. • Она может происходить, когда в этом участвует более двух потоков и двух синхронизированных объектов. Пример: public class DeadLock { static class Friend { private final String name; public Friend(String name) { this.name = name; } public String getName() { return this.name; } public synchronized void bow(Friend bower) { System.out.format("%s: %s has bowed to me!%n", this.name, bower.getName()); bower.bowBack(this); } public synchronized void bowBack(Friend bower) { System.out.format("%s: %s has bowed back to me!%n", this.name, bower.getName()); } } public static void main(String[] args) { final Friend alphonse = new Friend("Alphonse"); final Friend gaston = new Friend("Gaston"); new Thread(new Runnable() { public void run() { alphonse.bow(gaston); } }).start(); new Thread(new Runnable() { public void run() { gaston.bow(alphonse); } }).start(); } } Вывод: Alphonse: Gaston has bowed to me! Gaston: Alphonse has bowed to me! Программа заблокирована. Слегка изменим программу добавив вызов метода join(); public class DeadLock { static class Friend { private final String name; public Friend(String name) { this.name = name; } public String getName() { return this.name; } public synchronized void bow(Friend bower) { System.out.format("%s: %s" + " has bowed to me!%n", this.name, bower.getName()); bower.bowBack(this); } public synchronized void bowBack(Friend bower) { System.out.format("%s: %s" + " has bowed back to me!%n", this.name, bower.getName()); } } public static void main(String[] args) { final Friend alphonse = new Friend("Alphonse"); final Friend gaston = new Friend("Gaston"); Thread t= new Thread(new Runnable() { public void run() { alphonse.bow(gaston); } }); t.start(); try { t.join(); } catch (InterruptedException ex) { Logger.getLogger(DeadLock.class.getName()).log(Level.SEVERE, null, ex); } new Thread(new Runnable() { public void run() { gaston.bow(alphonse); } }).start(); } } Вывод: Alphonse: Gaston has bowed to me! Gaston: Alphonse has bowed back to me! Gaston: Alphonse has bowed to me! Alphonse: Gaston has bowed back to me! BUILD SUCCESSFUL (total time: 0 seconds) LiveLock - очень редкая ситуация когда потоки синхронно блокируют и отпускают ресурсы таким образом, что работать с ними не возможно Работа со статическими методами У статического метода нет ссылки this, как при этом работает механизм захвата ресурса, то есть чей же монитор захватывается. Поскольку нет ссылки this то захватывается монитор объект класса Class, который является кандидатом на использование его монитора для синхронизации статических методов. Пример: public class MyClass{ public static synchronized void myMethod(){ //code } } ... эквивалентна такой: public class SomeClass{ public static void myMethod(){ synchronized(SomeClass.class){ //code } } } Еще одно замечание. Объекты класса Class существуют в единственном экземпляре только в пределах одного ClassLoader-а. Следовательно, установив контекстный загрузчик классов потоку – у разных потоков могут быть разные экземпляры одного и того же класса Class и, следовательно, будет возможен одновременный вызов синхронизированных статических. Говоря простыми словами статический метод синхронизируется блокируя класс вместо объекта, и он будет блокировать класс, потому что ключевое слово статические означает: "класс вместо экземпляра". Ключевое слово synchronized означает, что только один поток может получить доступ к методу в одновременно. И вместе они означают: "Только один доступ к классу в одновременно". ./test.html