Як асинхронно викликати метод на 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.