ThreadPool не выполняет задачи последовательно

Я использую фреймворк Executor специально Executors.newCachedThreadPool();
У меня есть список Runnable, например. 100.
Каждое из первых 50 создает значение (хранящееся в списке), которое будет использоваться последними 50.
Я подумал, что если я передам Runnable в executor.execute() в том порядке, в котором они находятся в списке, они также будут выполняться в том же порядке.
Но этого не происходит.
Кажется, что задачи выполняются в случайном порядке, и они чередуются, а не выполняются последовательно.
Так ли это, предположим? работать? Любой способ обойти эту проблему?

Спасибо


person Cratylus    schedule 17.12.2010    source источник
comment
Executors.execute() не дает никаких гарантий упорядочения. Рабочие будут брать из очереди по порядку, но могут быть переключены в контекст в любое время до завершения. Таким образом, если у вас есть 50 потоков, список может пропустить до 50 задач вперед.   -  person OrangeDog    schedule 17.12.2010
comment
Порядок гарантируется только в том случае, если у вас есть один поток. В противном случае следующий свободный поток запустит следующую доступную задачу.   -  person Peter Lawrey    schedule 17.12.2010


Ответы (3)


Вам необходимо отправить задания двумя пакетами или иным образом создать явное отношение «происходит до». Предложите создать два пакета заданий и использовать invokeAll(batch1); invokeAll(batch2);. Метод invokeAll() выполнит все задания и заблокирует их до завершения. Возможно, вам придется обернуть ваши Runnable как Callable, что вы можете сделать с Executors.callable(Runnable r). (@Cameron Skinner опередил меня, получив пример кода, см. этот ответ для получения дополнительной информации...)

Весь смысл исполнителей заключается в том, чтобы абстрагироваться от специфики выполнения, поэтому порядок не гарантируется, если это явно не указано. Если вы хотите строго последовательное выполнение, делайте это в потоке, в котором вы работаете (самый простой), делайте это в однопоточном исполнителе, аля Executors.newSingleThreadExecutor(), или явно синхронизируйте задачи. Если вы хотите сделать последнее, вы можете использовать барьер или защелку и заблокировать зависимые задачи на барьере/защелке. Вы также можете сделать так, чтобы первый блок задач реализовывал Callable, возвращал Future, а зависимые задачи вызывали myFuture.get(), что заставляло бы их блокироваться до тех пор, пока не будут возвращены результаты.

Если вы расскажете больше о своем конкретном приложении, мы сможем помочь более конкретно.

person andersoj    schedule 17.12.2010
comment
Спасибо за подробную информацию!!! Не могли бы вы подробнее рассказать об использовании барьера, пожалуйста? Я не уверен, что понял эту часть. - person Cratylus; 17.12.2010
comment
@user384706: Ну, барьер, вероятно, является излишним для этой задачи, но концептуально, если у вас есть пакеты задач, представляющие собой единицу работы, которые необходимо выполнить вместе, прежде чем снова запустится другой пакет, вы можете использовать download.oracle.com/javase/6/docs/api/java /util/concurrent/ для этого. В вашем случае это тяжеловесно - чаще используется, когда у вас много итераций, и у вас есть только партия 1 -> партия 2. - person andersoj; 17.12.2010

Это правильное поведение. У вас нет никаких гарантий относительно того, в каком порядке выполняются Runnables.

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

Например,

for (Future<Whatever> f: service.invokeAll(first50tasks)) {
    addResultToList(f.get());
}
Future<Whatever> output = service.invokeAll(second50tasks);
person Cameron Skinner    schedule 17.12.2010
comment
В чем разница между выполнением invokeAll(tasks) и отправкой каждого вызываемого объекта по очереди? Это более оптимизировано для выполнения invokeAll? - person Cratylus; 17.12.2010
comment
invokeAll возвращается только после завершения всех задач, поэтому вам не нужно писать код для ожидания. - person Cameron Skinner; 17.12.2010

Возможно, вы могли бы execute первые 50, shutdown и awaitTermination, и только потом execute остальные 50? См. этот ответ для примера кода.

person Fabian Steeg    schedule 17.12.2010
comment
Я не уверен, как использовать awaitTermination;. Javadoc говорит: ‹b›Блокируется до тех пор, пока все задачи не завершат выполнение после запроса на завершение работы, или не истечет время ожидания, или текущий поток не будет прерван, в зависимости от того, что произойдет раньше‹/b›Я не хочу останавливать исполнителя и не уверен, что хочу тайм-аут - person Cratylus; 17.12.2010
comment
Нет причин выбрасывать совершенно хорошего исполнителя только для того, чтобы убедиться, что задания выполнены... invokeAll() делает это, или вы можете собрать Future и дождаться их завершения. - person andersoj; 17.12.2010
comment
@ user384706: Пример кода о том, как использовать awaitTermination, связан с моим ответом. Однако ответ Андерсоя звучит как правильный поступок. - person Fabian Steeg; 17.12.2010