делать синхронизированные вызовы очереди java-методов?

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

Допустим, у нас есть класс:

class Astore {
    ...
    public synchronized void a() {
        doSomethingTimeConsuming();
    }
    ...
}

и 3 потока, которые вызывают astore.a ()

final Astore astore = new Astore();

Thread t1 = new Thread(new Runnable() {
    public void run() { 
        astore.a();
        doSomethingElse();
        astore.a();
    }
});
t1.start();

Thread t2 = new Thread(new Runnable() {
    public void run() {
        astore.a();
    }
});
t2.start();

Thread t3 = new Thread(new Runnable() {
    public void run() {
        astore.a();
    }
});
t3.start();

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

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

  1. t1 (так его называли первым)
  2. t2 (был назван после T1)
  3. t3
  4. t1 снова (он уже был занят чем-то с A, пока другие потоки запрашивали метод)

Могу ли я с уверенностью предположить, что это будет поведение, или нет гарантии, что это будет порядок (или, что еще хуже, t2 и t3 могут быть вызваны в случайном порядке)

Какова лучшая практика, когда нескольким потокам может потребоваться обмен данными (например, сервер сокетов с одним потоком для каждого активного соединения - я не хочу, чтобы 6 клиентов истекали по тайм-ауту, ожидая, пока первый завершит огромную загрузку в общая структура данных)


person user3002166    schedule 20.05.2015    source источник
comment
What is the best practice when multiple threads may need to share data (for instance a socket server with one thread for each active connection Это вопрос на миллиард долларов. Целые библиотеки могут быть заполнены книгами только по этому предмету. Вероятнее всего, будет дан ответ на более конкретный вопрос.   -  person biziclop    schedule 20.05.2015
comment
В конкретном случае, о котором вы спрашивали, вам необходимо уменьшить размер блока и / или разделить процессы загрузки и импорта.   -  person chrylis -cautiouslyoptimistic-    schedule 20.05.2015
comment
... или, если структура данных огромна, вы все равно можете захотеть сохранить ее в базе данных, и в этот момент мы говорим об обработке транзакций.   -  person biziclop    schedule 20.05.2015
comment
Я не хочу, чтобы у 6 клиентов истекло время ожидания, пока первый завершит огромную загрузку в общую структуру данных - я предполагаю, что вы хотите предотвратить бедствия. Если у 6 клиентов для достижения этой цели истекло время ожидания, пусть будет так. Теперь, если эти тайм-ауты МОГУТ произойти, у вас есть проблемы с архитектурным дизайном, которые нельзя винить из-за того, что вам нужна синхронизация.   -  person Gimby    schedule 20.05.2015
comment
doSomethingTimeConsuming() - ошибка. Одно из важнейших советов, которым вы можете следовать, - старайтесь, чтобы ваши synchronized блоки были как можно меньше. Настоящее искусство многопоточного программирования состоит в том, чтобы спроектировать вашу программу так, чтобы ее потоки не тратили время на ожидание друг друга, когда есть работа, которую они могут выполнять. Если ваша программа выполняет какие-либо операции ввода-вывода внутри блока synchronized или синхронизированный блок обновляет больше переменных, чем вы можете сосчитать на пальцах, тогда вы можете пересмотреть свой дизайн.   -  person Solomon Slow    schedule 20.05.2015
comment
Данные хранятся в базе данных, и осуществляется доступ к программе выделенного сервера, проблема в том, что я хотел быть уверенным, что, когда несколько клиентов начнут передачу, данные будут поступать в том порядке, в котором они были отправлены (так что это будет перекрестная проверка с меткой времени). Структура небольшая, несколько персонажей одновременно, например, P1 двигаться вверх, 1 P1 двигаться вправо, 1 P1 стрелять. Я в основном хочу убедиться, что синхронизация не изменит внезапно порядок выполнения (сложно объяснить ограничение на количество символов)   -  person user3002166    schedule 20.05.2015
comment
Ошибочно полагать, что поток t1 вызовет astore.a() до того, как его вызовет какой-либо из двух других потоков. Каждый из трех вызовов start() создает новый поток и делает его работоспособным. Но runnable - это не то же самое, что run. Вполне возможно, что все три вызова start() завершатся до того, как какой-либо из новых потоков действительно начнет работать, и операционная система полностью решает, в каком порядке они будут запускаться.   -  person Solomon Slow    schedule 20.05.2015
comment
@jameslarge - я не знал, что поток будет держать блокировку. Я надеялся, что он освободит блокировку, как только выйдет из метода a (), чтобы другие потоки могли использовать a () без потока T1 для завершения doSomethingTimeConsuming () и повторно получить блокировку только после завершения большого метода. Проще говоря, мне было интересно, стоит ли он в очереди вызывает себя, чтобы мне не приходилось об этом беспокоиться. Вид сборки мусора на Яве.   -  person user3002166    schedule 20.05.2015
comment
Когда несколько клиентов независимо отправляют данные, порядок их отправки отсутствует. Единственный способ, которым данные от нескольких клиентов могут иметь значимый порядок, - это если порядок определяется каким-либо протоколом, и клиенты координируются друг с другом, чтобы подчиняться протоколу.   -  person Solomon Slow    schedule 20.05.2015
comment
Похоже, вам нужны атомарные транзакции. Если ваша база данных предоставляет атомарные транзакции, напишите свои клиентские потоки, чтобы использовать их, и повторите попытку, когда транзакция не удалась. Если база данных не предоставляет атомарные транзакции, я бы определил объект AtomicRequest, который клиентские потоки могут помещать в очередь, и у меня был бы отдельный поток, который потребляет AtomicRequests из очереди и обновляет базу данных.   -  person Solomon Slow    schedule 20.05.2015
comment
Клиенты отправляют только стандартные сообщения через сокетное соединение. Это сервер, у которого есть поток для каждого клиента. Я сам могу поставить в очередь запрос к базе данных, проблема заключалась в том, что когда несколько клиентов записывают в свои сокеты, серверной части придется ждать, пока очередь не станет их. Думаю, мне придется написать дополнительный класс, который будет ставить сообщения в очередь для сохранения в базе данных.   -  person user3002166    schedule 20.05.2015


Ответы (3)


Нет, он не будет ставить вызовы метода в очередь.

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

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

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

person folkol    schedule 20.05.2015
comment
Значит, другие потоки будут ждать ... например, ждать в очереди? - person Kayaman; 20.05.2015
comment
Нет, заказанной очереди нет. Я отредактировал свой ответ, чтобы уточнить. - person folkol; 20.05.2015
comment
@Kayaman Они будут ждать, но не в упорядоченной очереди, а просто слоняются вокруг, и планировщик может выбрать любого из них, когда монитор станет доступным. - person biziclop; 20.05.2015
comment
@folkol Отлично, это разъяснение, которое я искал (для ответа). Вы также можете упомянуть, что некоторые java.util.concurrent классы предоставляют справедливую функциональность очереди, чтобы сделать это отличным ответом. - person Kayaman; 20.05.2015
comment
Вы имеете в виду, кроме ReentrantLock? - person folkol; 20.05.2015
comment
Итак, в этом примере T1 вызовет метод дважды, после чего у меня нет возможности ожидать, какая из ступеней будет вызвана следующей. Спасибо за разъяснения. Я проверю ReentrantLock - думаю, создание настраиваемого потока для постановки в очередь и планирования вещей самому не было бы хорошей практикой? - person user3002166; 20.05.2015
comment
У вас даже нет гарантии, что t1 войдет первым. У вас НЕТ гарантии относительно порядка входа в этот синхронизированный блок. Если порядок вызовов важен, используйте ReentrantLock, как указано выше, или добавьте их в очередь, или позвольте одному и тому же потоку обрабатывать все вызовы (вручную или с помощью однопоточного Executor). Что касается огромной загрузки с блокировкой: если вам действительно нужно синхронизировать общий объект, вам просто придется смириться с поведением блокировки. Альтернативой является неблокирующий ввод-вывод или блокировка подресурса общего состояния. - person folkol; 20.05.2015

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

person Sunil Rajashekar    schedule 20.05.2015

folkol верен.

На самом деле это зависит от конструкции машины (ЦП).

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

person Hakuna Matata    schedule 20.05.2015