Comments 37
Я не заметил разницы, если честно.
На черновик похоже.
Правки действительно минимальны.
Не знаю как вернуть пост в состояние черновика!
По мотивам этого топика можно сделать интересный пост «О том, как я убирал статью в черновики, или в поисках утраченной кнопки».
Целый пост писать лень, но не могу не высказать удивления тому, что пока я косячил, было много ехидных комментариев, а когда благодаря ffriend статью дописал, все (пораженные?) замолчали.
Тривиальное решение просто. Но плюса заслуживает. Люблю когда велосипедов не изобретают.
Upd: по заголовку я было подумал что будет написан Java Agent, который модифицирует байткод класса с рапаралеливаемым методом.
Upd: по заголовку я было подумал что будет написан Java Agent, который модифицирует байткод класса с рапаралеливаемым методом.
Просто первая реакция после прочтения полной версии статьи «лучше бы и не дописывал». Статья уровня «ай, я нашел класс в стандартной библиотеке, надо срочно всем об этом рассказать». Тривиал.
И не могу не заметить, что это не первая Ваша статья на хабре.
Т.е. Вы по идее уже должны знать куда там кликать и что нажимать.
И не могу не заметить, что это не первая Ваша статья на хабре.
Т.е. Вы по идее уже должны знать куда там кликать и что нажимать.
Немного непонятно что за trueExecutor такой.
Это реальный Executor, исполняющий задачи. SerialExecutor сам не исполняет, а передает на исполнение реальному в порядке очереди, так что в исполнении каждый раз не более одной задачи от данного SerialExecutor'а. Это позволяет не вводить синхронизацию в распараллеливаемый класс.
Вопрос в том, какая именно имплементация Executor предлагается для использования в качестве trueExecutor. Пойдет любой?
В коде то синтаксическая ошибка — trueExecutor не определен.
Если предполагается что можно передать любой Executor извне, может быть проблема с тем что передадут не какой-нибудь ThreadPoolExecutor, а уже SerialExecutor который враппит ThreadPoolExecutor, и получится двойной враппинг.
В коде то синтаксическая ошибка — trueExecutor не определен.
Если предполагается что можно передать любой Executor извне, может быть проблема с тем что передадут не какой-нибудь ThreadPoolExecutor, а уже SerialExecutor который враппит ThreadPoolExecutor, и получится двойной враппинг.
Зачем Вы привели свою реализацию SingleThreadExecutor, она, скажем мягко, содержит грубые ошибки?
И чем вам не подошел вариант с java.util.concurrent.Executors.newSingleThreadExecutor?
И чем вам не подошел вариант с java.util.concurrent.Executors.newSingleThreadExecutor?
Мне не нужен был SingleThreadExecutor ни в каком виде, и я привел свою реализацию SerialExecutor. Это разные вещи. Вы считаете, что мой SerialExecutor содержит ошибки? Какие?
Для метода
Про ошибки: если в ваш
newSingleThreadExecutor
в доках явно сказано «все задачи выполняются в одном потоке последовательно, не более одной одновременно». Ну и реализуется интерфейс ExecutorService
, а не Executor
. Первый является расширением второго, так что все ок. Напишите, пожалуйста, чем же Вам не подошел этот вариант?Про ошибки: если в ваш
SerialExecutor
засылать таски из нескольких потоков, то таска может выполнится более одного раза, и даже одновременно.anjensan: При использовании SingleThreadExecutor на каждый экземпляр распараллеливаемого класса будет заводиться свой Thread. В общем случае это слишком накладно.
Насчет SerialExecutor и таски из нескольких потоков, там стоит synchronized(tasks) и ужасы, которые вы обещаете, исключены. Если не верите, напишите опровергающий тест.
Насчет SerialExecutor и таски из нескольких потоков, там стоит synchronized(tasks) и ужасы, которые вы обещаете, исключены. Если не верите, напишите опровергающий тест.
Хм, ваша правда. Ужасы мне померещились :)
Во-первых, если очень хочется написать свой SerialExecutor, как недавно сделал я по незнанию, используйте LinkedBlockingQueue — тогда synchronized (tasks) не нужно:
docs.oracle.com/javase/6/docs/api/java/util/concurrent/LinkedBlockingQueue.html
Во-вторых, вот:
Всё, у вас теперь только один Thread, как вы и хотели. Меньше кода, эффект тот же.
docs.oracle.com/javase/6/docs/api/java/util/concurrent/LinkedBlockingQueue.html
Во-вторых, вот:
public class SerialExecutor {
private static final ExecutorService executorService = Executors.newSingleThreadExecutor();
public static void execute(final Runnable task) {
executorService.submit(task);
}
}
class ServiceWrapper extends Service {
public void longJob(final Object arg) {
SerialExecutor.execute(new Runnable() {
public void run() {
ServiceWrapper.super.longJob(arg);
}
});
}
}
Всё, у вас теперь только один Thread, как вы и хотели. Меньше кода, эффект тот же.
Если бы я хотел только один Thread, то я бы вовсе не определял SerialExecutor, а использовал бы результат newSingleThreadExecutor напрямую, и кода было бы еще меньше:
Но мне SingleThreadExecutor не был нужен, как я и объяснял выше anjensan. Мне нужно чтобы потоков использовалась оптимальное число — чтобы использовать все процессоры, но не плодить по потоку на объект — объектов может быть слишком много. Описать создание такого Executor'а, подходящего на все случаи жизни, невозможно, поэтому его создание оставлено пользователю.
class ServiceWrapper extends Service {
private static final ExecutorService executorService = Executors.newSingleThreadExecutor();
public void longJob(final Object arg) {
executorService.execute(new Runnable() {
public void run() {
ServiceWrapper.super.longJob(arg);
}
});
}
}
Но мне SingleThreadExecutor не был нужен, как я и объяснял выше anjensan. Мне нужно чтобы потоков использовалась оптимальное число — чтобы использовать все процессоры, но не плодить по потоку на объект — объектов может быть слишком много. Описать создание такого Executor'а, подходящего на все случаи жизни, невозможно, поэтому его создание оставлено пользователю.
Использование LinkedBlockingQueue или любой другой синхронизированной коллекции менее оптимально, так как требует на обработку каждой задачи 2 обращения к синхронизированному объекту (сначала взять без удаления и только после обработки удалить), вместо одного у меня. Дело в том, что нужно поддерживать признак «объект уже в работе, не передавать Runnable в Executor».
Что-то я не вкурил, зачем брать задачу из очереди без удаления. Поясните, я действительно не понимаю. Лично мне кажется, что вы неправильно понимаете суть LinkedBlockingQueue. Нормальное её использование предполагает извлечение из очереди объекта и работу с ним. При этом, если очередь пустая, поток, извлекающий данные, замораживается до поступления в очередь новых данных. Добавление новых данных в очередь не замораживает поток, из которого данные добавляются. Если вы передаёте Executor своему SerialExecutor'у с целью выбирать между однопоточной или многопоточной обработкой задач, тогда вообще не нужно писать никакие SerialExecutor'ы, а просто воспользоваться или Executors.newSingleThreadExecutor(), или Executors.newFixedThreadPool(int nThreads). Они сами позаботятся о синхронизации своих очередей, и всё, что вам нужно — это дать им задачу в одном из двух видов:
- Runnable, если нужно просто закинуть задачу на выполнение.
- Callable, если нужен результат вычислений. Его можно получить так:
class Service { public String /* чисто для примера */ longJob(Object arg) {...} } ... final Future<Result> future = someExecutorService.submit(new Callable<Result>() { public String call() { return someService.longJob(arg); } }); // отдаёте ваш future, куда надо ... // Где надо: final String result = future.get();
Я всё ещё не понял, какие такие специальные задачи вы решаете, что вам не подходит всё богатство стандартной библиотеки Java.
Брать задачу из очереди без удаления нужно для того, чтобы показать потоку-поставщику, что SerialExecutor уже работает и не нужно запускать его второй раз. Можно для описания этого состояния завести отдельную переменную, но тогда надо синхронизироваться одновременно по очереди и этой переменной, и внутренняя синхронизация LinkedBlockingQueue оказывается неиспользуемой. Собственно, я так и сделал, и вместо блокирующей очереди использовал несихронизированный LinkedList.
Задача же была такая: распараллелить исполнение отдельных методов с использованием пула потоков, а не расходуя по потоку на вызов метода. При этом вызовы методов, относящихся к одному и тому же объекту, в том числе повторные вызовы одного и того же метода, не должны пересекаться по времени, так как иначе придется вводить дополнительную синхронизацию по доступу к полям объекта.
Задача же была такая: распараллелить исполнение отдельных методов с использованием пула потоков, а не расходуя по потоку на вызов метода. При этом вызовы методов, относящихся к одному и тому же объекту, в том числе повторные вызовы одного и того же метода, не должны пересекаться по времени, так как иначе придется вводить дополнительную синхронизацию по доступу к полям объекта.
И насчет богатства стандартной библиотеки — это богатство имеет существенный изъян — нет средств организации массива задач, исполняемых на пуле потоков. Есть только возможность запустить задачу и дождаться ее завершения с помощью Future, но так ждать можно лишь из потока, а не из задачи, иначе задача заблокирует пуловский поток, что приведет к дедлоку ограниченный пул (thread starvation), а неограниченый пул приведет к конфигурации «поток на задачу», что противоречит самой идее пула потоков. SerialExecutor и призван закрыть одну из дыр стандартной библиотеки.
Sign up to leave a comment.
Распараллеливание с минимальными правками в коде