Как асинхронно вызвать метод в Java

128

В последнее время я смотрел горутины Go и подумал, что было бы неплохо иметь что-то подобное в Java. Насколько я искал, обычный способ распараллеливания вызова метода - это сделать что-то вроде:

final String x = "somethingelse";
new Thread(new Runnable() {
           public void run() {
                x.matches("something");             
    }
}).start();

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

Я опубликовал свой класс-оболочку в J-Go . Но я не знаю, хорошее ли это решение. Использование простое:

SampleClass obj = ...
FutureResult<Integer> res = ...
Go go = new Go(obj);
go.callLater(res, "intReturningMethod", 10);         //10 is a Integer method parameter
//... Do something else
//...
System.out.println("Result: "+res.get());           //Blocks until intReturningMethod returns

или менее подробный:

Go.with(obj).callLater("myRandomMethod");
//... Go away
if (Go.lastResult().isReady())                //Blocks until myRandomMethod has ended
    System.out.println("Method is finished!");

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

Я хочу получить некоторое мнение о моей крошечной библиотеке и о том, как делать такие вызовы асинхронных методов в Java. Это безопасно? Есть ли уже более простой способ?

Фелипе Хуммель
источник
1
Можете ли вы снова показать свой код J-Go lib?
msangel
Я работал над проектом, в котором я читал потоковые символы. После того, как слово прочитано полностью, я выполнял много операций над этим словом. Наконец я положил это в коллекцию. Как только все данные из потока прочитаны, я возвращаю ответ. Я решил запускать поток каждый раз, когда выполняю операцию над словом. Но это снизило общую производительность. Затем я узнал, что потоки сами по себе - дорогостоящая операция. Я не уверен, что запуск потока для одновременного вызова метода может повысить производительность, пока он не выполнит какие-либо тяжелые операции ввода-вывода.
Амит Кумар Гупта
2
Отличный вопрос !! Java 8 предоставляет для этого CompletableFutures. Другие ответы основаны на, вероятно, старых версиях Java
TriCore

Ответы:

156

Я только что обнаружил, что есть более чистый способ

new Thread(new Runnable() {
    public void run() {
        //Do whatever
    }
}).start();

(По крайней мере, в Java 8) вы можете использовать лямбда-выражение, чтобы сократить его до:

new Thread(() -> {
    //Do whatever
}).start();

Так же просто, как создать функцию на JS!

AegisHexad
источник
Ваш ответ помог моей проблеме - stackoverflow.com/questions/27009448/… . Немного сложно применить мою ситуацию, но в конце концов все получилось :)
Декард
Как позвонить с параметрами?
yatanadam
2
@yatanadam Это может ответить на ваш вопрос. Просто поместите приведенный выше код в метод и вставьте переменную, как обычно. Посмотрите этот тестовый код, который я сделал для вас.
user3004449
1
@eNnillaMS Нужно ли останавливать поток после запуска? Он останавливается автоматически или сборщиком мусора ?
user3004449
@ user3004449 Поток останавливается автоматически, но вы также можете его принудительно остановить.
Shawn
59

Java 8 представила CompletableFuture, доступную в пакете java.util.concurrent.CompletableFuture, который можно использовать для выполнения асинхронного вызова:

CompletableFuture.runAsync(() -> {
    // method call or code to be asynch.
});
Рахул Чаухан
источник
CompletableFutureбыл упомянут в другом ответе, но тот использовал всю supplyAsync(...)цепочку. Это простая обертка, которая идеально подходит под вопрос.
ndm13 05
ForkJoinPool.commonPool().execute()имеет немного меньше накладных расходов
Марк Иеронимус
31

Вы можете также рассмотреть класс java.util.concurrent.FutureTask.

Если вы используете Java 5 или новее, FutureTask представляет собой готовую реализацию « отменяемого асинхронного вычисления».

В java.util.concurrentпакете доступны даже более широкие возможности асинхронного планирования выполнения (например, ScheduledExecutorService), но они FutureTaskмогут иметь все необходимые вам функции.

Я бы даже сказал, что больше не рекомендуется использовать первый шаблон кода, который вы привели в качестве примера с тех пор, как он FutureTaskстал доступен. (Предполагая, что вы используете Java 5 или новее.)

shadit
источник
Дело в том, что я просто хочу выполнить один вызов метода. Таким образом, мне пришлось бы изменить реализацию целевого класса. То, что я хотел, - это точно позвонить, не беспокоясь о влиянии Runnable или Callable,
Фелипе Хаммел
Я слышу тебя. В Java (пока) нет первоклассных функций, так что сейчас это состояние искусства.
шадит
спасибо за ключевые слова "future" ... сейчас я открываю обучающие материалы о них ... очень полезно. : D
gumuruh 07
4
-1, насколько я могу судить, FutureTask сама по себе недостаточна для асинхронного запуска чего-либо. Вам все равно нужно создать поток или исполнитель для его запуска, как в примере Карлоса.
MikeFHay
24

Мне не нравится идея использовать для этого Reflection.
Это опасно не только из-за того, что его нельзя пропустить при рефакторинге, но и из-за того, что он может быть отвергнут SecurityManager.

FutureTask- хороший вариант, как и другие варианты из пакета java.util.concurrent.
Мои любимые для простых задач:

    Executors.newSingleThreadExecutor().submit(task);

немного короче, чем создание потока (задача - вызываемая или выполняемая)

user85421
источник
1
Дело в том, что я просто хочу выполнить один вызов метода. Таким образом, мне пришлось бы изменить реализацию целевого класса. То, что я хотел, - это точно позвонить, не беспокоясь о влиянии Runnable или Callable
Фелипе Хаммел
тогда это не сильно поможет :( но обычно я бы предпочел использовать Runnable или Callable вместо Reflection
user85421
4
Небольшое примечание: лучше следить за ExecutorServiceэкземпляром, поэтому вы можете позвонить, shutdown()когда вам нужно.
Daniel Szalay
1
Если бы вы сделали это, не могли бы вы закончить работу с незакрытым ExecutorService, из-за чего ваша JVM откажется завершать работу? Я бы порекомендовал написать свой собственный метод для получения однопоточной, ограниченной по времени ExecutorService.
djangofan
14

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

например:

 CompletableFuture.supplyAsync(this::findSomeData)
                     .thenApply(this:: intReturningMethod)
                     .thenAccept(this::notify);

Более подробную информацию можно найти в этой статье.

Таль Ависсар
источник
10

Вы можете использовать @Asyncаннотацию из jcabi-аспектов и AspectJ:

public class Foo {
  @Async
  public void save() {
    // to be executed in the background
  }
}

Когда вы вызываете save(), запускается новый поток и выполняет свое тело. Ваш основной поток продолжается, не дожидаясь результата save().

yegor256
источник
1
Обратите внимание, что при таком подходе все вызовы save будут асинхронными, что может быть, а может и не быть тем, что мы хотим. В случаях, когда только некоторые вызовы данного метода должны выполняться асинхронно, можно использовать другие подходы, предложенные на этой странице.
bmorin
Могу ли я использовать его в веб-приложении (поскольку управление потоками там не рекомендуется)?
onlinenaman 05
8

Для этого вы можете использовать Future-AsyncResult.

@Async
public Future<Page> findPage(String page) throws InterruptedException {
    System.out.println("Looking up " + page);
    Page results = restTemplate.getForObject("http://graph.facebook.com/" + page, Page.class);
    Thread.sleep(1000L);
    return new AsyncResult<Page>(results);
}

Ссылка: https://spring.io/guides/gs/async-method/

user3177227
источник
10
если вы используете spring -boot.
Джованни Торальдо,
6

Java также предоставляет удобный способ вызова асинхронных методов. в java.util.concurrent у нас есть ExecutorService, который помогает делать то же самое. Инициализируйте свой объект следующим образом -

 private ExecutorService asyncExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

а затем вызовите функцию, например:

asyncExecutor.execute(() -> {

                        TimeUnit.SECONDS.sleep(3L);}
Ананд
источник
3

Вероятно, это не настоящее решение, но теперь - в Java 8 - вы можете сделать этот код хотя бы немного лучше, используя лямбда-выражение.

final String x = "somethingelse";
new Thread(() -> {
        x.matches("something");             
    }
).start();

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

new Thread(() -> x.matches("something")).start();
kcpr
источник
Это действительно приятно использовать с Java 8.
Mukti
3

Вы можете использовать AsyncFuncот Cactoos :

boolean matches = new AsyncFunc(
  x -> x.matches("something")
).apply("The text").get();

Он будет выполнен в фоновом режиме, а результат будет доступен в get()виде файла Future.

yegor256
источник
2

На самом деле это не связано, но если бы я должен был асинхронно вызвать метод, например, match (), я бы использовал:

private final static ExecutorService service = Executors.newFixedThreadPool(10);
public static Future<Boolean> matches(final String x, final String y) {
    return service.submit(new Callable<Boolean>() {

        @Override
        public Boolean call() throws Exception {
            return x.matches(y);
        }

    });
}

Затем для вызова асинхронного метода я бы использовал:

String x = "somethingelse";
try {
    System.out.println("Matches: "+matches(x, "something").get());
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

Я проверил это, и он работает. Просто подумал, что это может помочь другим, если они просто пришли за «асинхронным методом».

FlameBlazer
источник
1

Еще есть хорошая библиотека для Async-Await, созданная EA: https://github.com/electronicarts/ea-async

Из их Readme:

С EA Async

import static com.ea.async.Async.await;
import static java.util.concurrent.CompletableFuture.completedFuture;

public class Store
{
    public CompletableFuture<Boolean> buyItem(String itemTypeId, int cost)
    {
        if(!await(bank.decrement(cost))) {
            return completedFuture(false);
        }
        await(inventory.giveItem(itemTypeId));
        return completedFuture(true);
    }
}

Без EA Async

import static java.util.concurrent.CompletableFuture.completedFuture;

public class Store
{
    public CompletableFuture<Boolean> buyItem(String itemTypeId, int cost)
    {
        return bank.decrement(cost)
            .thenCompose(result -> {
                if(!result) {
                    return completedFuture(false);
                }
                return inventory.giveItem(itemTypeId).thenApply(res -> true);
            });
    }
}
витро
источник