Java: запуск Callable в отдельном процессе

Учитывая экземпляр x из Callable<T>, как я могу запустить x в отдельном процессе, чтобы я мог перенаправить стандартный ввод и вывод процесса? Например, есть ли способ построить Process из Callable? Существует ли стандарт Executor, который дает контроль над вводом и выводом?

[ОБНОВЛЕНИЕ] Неважно, что Callable выполняется в новом процессе, а не в новом потоке. Я хочу поместить экземпляр Callable в «привязку», чтобы я мог управлять его стандартным вводом/выводом. Насколько я знаю, это требует нового процесса.


person Chris Conway    schedule 04.12.2008    source источник


Ответы (4)


В более общем смысле:

Учитывая экземпляр x Callable, использующий глобальные переменные A и B, как я могу запустить x одновременно, чтобы x видел пользовательские значения для A и B, а не «исходные» значения A и B?

И лучший ответ — не использовать глобальные переменные. Зависимость вводит такие вещи. Расширьте Callable и добавьте методы setStdIn, setStdOut (и setStdErr, если вам это нужно).

Я знаю, что это не тот ответ, который вы ищете, но все решения, которые я видел для этого, требуют нового процесса, и единственный способ превратить этот Callable в новый процесс — это изменить код Callable так, он сериализуем, или предоставляет имя класса, или какой-то другой хак, поэтому вместо того, чтобы вносить изменения, которые дадут вам неприятное, хрупкое решение, просто сделайте это правильно *

* «правильно» использовать широко распространенный шаблон внедрения зависимостей для обеспечения слабой связи. YMMV

ОБНОВЛЕНИЕ: В ответ на комментарий 1. Вот ваш новый интерфейс:

import java.io.InputStream;
import java.io.PrintStream;
import java.util.concurrent.Callable;


public interface MyCallable<V> extends Callable<V> {
  void setStdIn(InputStream in);
  void setStdOut(PrintStream out);  
}

и ваши задачи будут выглядеть так:

import java.io.InputStream;
import java.io.PrintStream;


public class CallableTask implements MyCallable<Object> {

    private InputStream in = System.in;
    private PrintStream out = System.out;

    public void setStdIn(InputStream in) {
        this.in = in;
    }

    public void setStdOut(PrintStream out) {
        this.out = out;
    }

    public Object call() throws Exception {
        out.write(in.read());
        return null;
    }

}

Нет необходимости в процессе. Любое решение (даже использующее процессы) почти наверняка потребует каких-то изменений в коде вызываемых объектов. Это самое простое (просто замените System.out на this.out).

person noah    schedule 04.12.2008
comment
Как бы я реализовал ваше предложение, не ответив сначала на этот вопрос? - person Chris Conway; 05.12.2008
comment
Внедрение зависимостей — это не волшебная пыльца, которая решает всю проблему, знаете ли. - person Chris Conway; 05.12.2008
comment
Это не волшебство, но глобальные переменные хорошо известны как запах кода. Внедрение зависимостей — хорошая альтернатива. - person noah; 05.12.2008
comment
Что, если мне действительно нужен дополнительный процесс, потому что библиотека, которую я должен использовать, более или менее зависит от глобального синглтона? - person fho; 23.01.2013
comment
@Florian, вам нужно задать себе несколько вопросов: 1) Обхожу ли я намерение библиотеки? Если это так, вы, вероятно, столкнетесь с другими проблемами, и ваше решение может сломаться при обновлении библиотеки. 2) Является ли эта библиотека хорошим выбором? Могу ли я использовать что-то еще без глобальной зависимости? 3) Есть ли другой способ? Если после этого вам все еще нужен новый процесс, загляните в ProcessBuilder. - person noah; 23.01.2013
comment
@noah: 1) Да, я. Но я пишу тесты на связь через сокеты. И если используется тот же процесс/синглтон, все не отправляется. Затем получатель вызывается через синглтон. (что хорошо... но не для моего тестирования) 2) Это не так, но альтернативы нет. 3) Я нашел gfork, который позволяет мне запускать Runnables в отдельном процессе... но я не могу его использовать, потому что он зависит от Serializable, и я не могу ввести его в наши данные. ... в конце концов, я думаю, мне просто не повезло :) - person fho; 24.01.2013
comment
@Florian, если вы тестируете сокеты, я думаю, вам обязательно нужно иметь два отдельных процесса ... Так или иначе, вы должны сериализовать данные через сокеты. - person noah; 24.01.2013
comment
Да ... но один способ сериализации обрабатывается библиотекой ... другой способ (тестирование) будет обрабатываться мной ... теперь ваша очередь сказать, что тестирование сторонних библиотек не имеет смысла :) - person fho; 24.01.2013
comment
Почему бы не позволить библиотеке обрабатывать сериализацию в вашем тесте? - person noah; 29.01.2013

stdin/stdout можно перенаправить для всей системы, но я не уверен, что его можно перенаправить для одного потока — вы всегда просто получаете его из System. (Однако вы можете заставить System.out перейти к другому потоку, я использовал это для перехвата трассировки стека до того, как появился метод получения трассировки в виде строки)

Вы можете перенаправить stdin/stdout, запустить вызываемый объект, а затем перенаправить его обратно, но если что-то еще использует System.out во время перенаправления, это также попадет в ваш новый файл.

Методы будут System.setIn() и System.setOut() (и System.setErr()), если вы хотите пойти по этому пути.

person Bill K    schedule 05.12.2008

Стремитесь структурировать свой код, используя параметризацию сверху, внедрение зависимостей или как бы вы это ни называли. Избегайте статики, даже тех, которые одеты как синглтоны или спрятаны в плохой библиотеке.

Если вы не хотите удалять использование System.in/out/err, вам повезло. Их можно установить глобально с помощью System.set(In/Out/Err). Чтобы иметь возможность выполнять разные потоки для отдельных потоков, установите реализацию, которая использует ThreadLocal для поиска цели для делегирования. Локальные потоки, как правило, злые, но могут быть полезны в неблагоприятных ситуациях.

Из пояснений следует, что исходному плакату не нужно создавать отдельный процесс.

person Tom Hawtin - tackline    schedule 05.12.2008

Взгляните на класс ProcessBuilder, он дает вам возможность захватить стандартный вывод и стандартный вывод запускаемого им процесса.

Вы можете создать класс Main, который запускает Callable, а затем запускать его как другую jvm с помощью ProcessBuilder. Класс Main может принимать имя класса вашего вызываемого объекта в качестве входного параметра командной строки и загружать его с помощью Class.forName() и Class.newInstance().

Альтернативой, если вы хотите, чтобы он запускал конкретный вызываемый экземпляр, является сериализация экземпляра в файл перед запуском другого процесса (это было бы очень грубой формой связи между двумя процессами).

Вопрос в том, почему вы хотите это сделать? вы не можете просто запустить свой вызываемый объект в другом потоке? Для этого в java.util.concurrent есть несколько полезных пулов потоков.

person Yoni    schedule 04.12.2008
comment
ProcessBuilder, AFAICT, хочет строку командной строки, а не Callable. Я определенно хочу вызвать экземпляр, а не создавать новый экземпляр на основе имени класса. Что касается процесса и потока, см. выше. - person Chris Conway; 05.12.2008
comment
Я имел в виду, что вы даете java.exe в качестве команды для ProcessBUilder, а для класса Main вы даете оболочку, которая запускает вызываемый объект. Конечно, вам нужно будет сериализовать экземпляр и передать имя файла в качестве параметра вашему основному классу. Или просто используйте System.setIn()/out() и т.д. - person Yoni; 06.12.2008