Как асинхронно вызвать метод в 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!


Ваш ответ помог моей проблеме - 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 или новее.)


Дело в том, что я просто хочу выполнить один вызов метода. Таким образом, мне пришлось бы изменить реализацию целевого класса. То, что я хотел, - это точно позвонить, не беспокоясь о влиянии Runnable или Callable,
— Фелипе Хаммел

Я слышу тебя. В Java (пока) нет первоклассных функций, так что сейчас это состояние искусства.
— шадит

спасибо за ключевые слова "future" ... сейчас я открываю обучающие материалы о них ... очень полезно. : D
— gumuruh 07

4
-1, насколько я могу судить, FutureTask сама по себе недостаточна для асинхронного запуска чего-либо. Вам все равно нужно создать поток или исполнитель для его запуска, как в примере Карлоса.
— MikeFHay

24

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

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

    Executors.newSingleThreadExecutor().submit(task);

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


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().


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/


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();

Это действительно приятно использовать с Java 8.
— Mukti

3

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

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

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


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();
}

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


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);
            });
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.