Як асинхронно викликати метод на Java


126

Нещодавно я переглядав goroutine Go і думав, що було б непогано мати щось подібне на Java. Наскільки я шукав поширений спосіб паралелізації виклику методу - це зробити щось на кшталт:

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

Це не дуже елегантно. Чи є кращий спосіб зробити це? Мені потрібно було таке рішення в проекті, тому я вирішив реалізувати власний клас обгортки навколо виклику методу async.

Я опублікував свій клас обгортки в 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
— Програміст

Відповіді:


155

Я щойно виявив, що існує більш чистий спосіб зробити своє

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

(Принаймні, на Java 8), ви можете використовувати лямбда-вираз, щоб скоротити його до:

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

Настільки ж просто, як зробити функцію в JS!


Ваша відповідь допомогла моїй проблемі - stackoverflow.com/questions/27009448/… . Трохи складно застосувати мою ситуацію, але в кінцевому підсумку це вийшло :)
— Deckard

Як телефонувати з параметрами?
— ятанадам

2
@yatanadam Це може відповісти на ваше запитання. Просто помістіть вищевказаний код всередину методу та вставте змінну, як зазвичай. Перевірте цей тестовий код, який я зробив для вас.
— user3004449

1
@eNnillaMS Чи потрібно припиняти потік після запуску? Це зупиняється автоматично, або сміттєзбірником ?
— user3004449

@ user3004449 Нитка зупиняється автоматично, однак ви також можете її примусити.
— Шон

58

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

CompletableFuture.runAsync(() -> {
    // method call or code to be asynch.
});

CompletableFutureзгадувалося в іншій відповіді, але ця використовувала весь supplyAsync(...)ланцюжок. Це проста обгортка, яка ідеально відповідає питанню.
— ndm13

ForkJoinPool.commonPool().execute()має трохи менші накладні витрати
— Марк Єронім

31

Ви можете також розглянути клас java.util.concurrent.FutureTask.

Якщо ви використовуєте Java 5 або новішу версію , FutureTask - це "під ключ" реалізація " Скасовувані асинхронні обчислення".

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

Я б навіть пішов так далеко, щоб сказати, що більше не доцільно використовувати перший шаблон коду, який ви подали як приклад з тих пір, як вони FutureTaskстали доступними. (Припустимо, що ви перебуваєте на Java 5 або новішої версії.)


Вся справа в тому, що я просто хочу виконати один виклик методу. Таким чином я повинен був би змінити реалізацію цільового класу. Мені дуже хотілося зателефонувати, не турбуючись про те, що ми можемо пограбувати на Runnable або Callable
— Felipe Hummel

Я чую тебе. Java ще не має першокласних функцій, тому це зараз сучасний стан.
— shadit

дякую за ключові слова "майбутнє" ... зараз я відкриваю підручники про них ... дуже корисно. : D
— gumuruh

4
-1 наскільки я можу сказати, FutureTask сам по собі недостатній, щоб запустити щось асинхронно. Ще потрібно створити Тему або Виконавця, щоб запустити її, як у прикладі Карлоса.
— MikeFHay

24

мені не подобається ідея використання Reflection для цього.
Не тільки небезпечний для того, щоб пропустити його в деяких рефакторингах, але його також можна відмовити SecurityManager.

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

    Executors.newSingleThreadExecutor().submit(task);

трохи трохи коротше , ніж створення потоку (завдання є Callable або Runnable)


1
Вся справа в тому, що я просто хочу виконати один виклик методу. Таким чином я повинен був би змінити реалізацію цільового класу. Чого я хотів, це саме дзвінок, не турбуючись про те, щоби втрутитися у біг або дзвінок
— Феліпе Гуммель

тоді це не допоможе багато :( але зазвичай я вважаю за краще використовувати Runnable або Callable замість Reflection
— user85421

4
Лише коротка примітка: приємніше слідкувати за ExecutorServiceінстанцією, тому ви можете телефонувати, shutdown()коли вам доведеться.
— Даніель Шалай

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
Зауважте, що такий підхід робив би всі дзвінки для збереження асинхронності, що може бути чи не бути тим, що ми хочемо. У випадках, коли асинхронізувати лише деякі виклики даного методу, можна використовувати інші підходи, запропоновані на цій сторінці.
— бморин

Чи можу я використовувати його у веб-додатку (оскільки керування потоками там не рекомендується)?
— onlinenaman

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
якщо ви використовуєте весняний завантаження.
— Джованні Торальдо

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від кактусів :

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.