Jak asynchronicznie wywołać metodę w Javie

128

Ostatnio przyglądałem się gorutynom Go i pomyślałem, że fajnie byłoby mieć coś podobnego w Javie. O ile szukałem, powszechnym sposobem zrównoleglenia wywołania metody jest zrobienie czegoś takiego:

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

To niezbyt eleganckie. Czy jest lepszy sposób na zrobienie tego? Potrzebowałem takiego rozwiązania w projekcie, więc zdecydowałem się zaimplementować własną klasę opakowującą wokół wywołania metody asynchronicznej.

Opublikowałem moją klasę wrapper w J-Go . Ale nie wiem, czy to dobre rozwiązanie. Użycie jest proste:

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

lub mniej szczegółowe:

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

Wewnętrznie używam klasy, która implementuje Runnable i wykonuję trochę pracy Reflection, aby uzyskać poprawny obiekt metody i wywołać go.

Chcę uzyskać opinię na temat mojej małej biblioteki i na temat wykonywania wywołań metod asynchronicznych w Javie. Czy to jest bezpieczne? Czy jest już prostszy sposób?

Felipe Hummel
źródło
1
Czy możesz ponownie pokazać swój kod J-Go lib?
msangel
Pracowałem nad projektem, w którym czytałem tekst strumieniowy. Po przeczytaniu całego słowa wykonywałem wiele operacji na tym słowie. Wreszcie włożyłem go do kolekcji. Po odczytaniu wszystkich danych ze strumienia zwracam odpowiedź. Postanowiłem rozpocząć wątek za każdym razem, gdy wykonuję operację na słowie. Ale zmniejszyło to ogólną wydajność. Wtedy dowiedziałem się, że wątki same w sobie są kosztowną operacją. Nie jestem pewien, czy uruchomienie wątku w celu jednoczesnego wywołania metody może spowodować zwiększenie wydajności, dopóki nie wykona żadnej ciężkiej operacji we / wy.
Amit Kumar Gupta
2
Świetne pytanie !! W tym celu Java 8 zapewnia CompletableFutures. Inne odpowiedzi są oparte na prawdopodobnie starych wersjach Javy
TriCore

Odpowiedzi:

157

Właśnie odkryłem, że istnieje lepszy sposób na zrobienie tego

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

(Przynajmniej w Javie 8) możesz użyć wyrażenia lambda, aby skrócić je do:

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

Tak proste, jak utworzenie funkcji w JS!

AegisHexad
źródło
Twoja odpowiedź pomogła w moim problemie - stackoverflow.com/questions/27009448/… . Trochę trudno zastosować moją sytuację, ale w końcu się udało :)
Deckard
Jak dzwonić z parametrami?
yatanadam
2
@yatanadam To może odpowiedzieć na twoje pytanie. Po prostu umieść powyższy kod wewnątrz metody i podaj zmienną, tak jak zwykle. Sprawdź ten kod testowy, który dla Ciebie zrobiłem.
user3004449
1
@eNnillaMS Czy wątek musi zostać zatrzymany po uruchomieniu? Czy zatrzymuje się automatycznie, czy przez odśmiecacz ?
user3004449
@ user3004449 Wątek zatrzymuje się automatycznie, ale możesz również to wymusić.
Shawn
59

Java 8 wprowadziła CompletableFuture dostępną w pakiecie java.util.concurrent.CompletableFuture, można użyć do wykonania połączenia asynchronicznego:

CompletableFuture.runAsync(() -> {
    // method call or code to be asynch.
});
Rahul Chauhan
źródło
CompletableFuturezostał wymieniony w innej odpowiedzi, ale ten wykorzystał cały supplyAsync(...)łańcuch. To proste opakowanie, które idealnie pasuje do pytania.
ndm13,
ForkJoinPool.commonPool().execute()ma nieco mniej narzutów
Mark Jeronimus
31

Możesz również rozważyć klasę java.util.concurrent.FutureTask.

Jeśli używasz języka Java 5 lub nowszego, FutureTask jest gotową do realizacji implementacją „asynchronicznych obliczeń z możliwością anulowania”.

W java.util.concurrentpakiecie dostępne są jeszcze bogatsze zachowania planowania wykonywania asynchronicznego (na przykład ScheduledExecutorService), ale FutureTaskmogą mieć wszystkie wymagane funkcje.

Posunąłbym się nawet do stwierdzenia, że ​​nie jest już wskazane używanie pierwszego wzorca kodu, który podałeś jako przykład, odkąd FutureTaskstał się dostępny. (Zakładając, że korzystasz z Java 5 lub nowszej).

shadit
źródło
Chodzi o to, że chcę tylko wykonać jedno wywołanie metody. W ten sposób musiałbym zmienić implementację klasy docelowej. To, czego chciałem, to dokładnie zadzwonić bez martwienia się o utrudnienie Runnable lub Calllable
Felipe Hummel
Słyszę cię. Java nie ma (jeszcze) funkcji pierwszej klasy, więc jest to obecnie najnowocześniejszy.
shadit
dzięki za „przyszłe” słowa kluczowe… teraz otwieram tutoriale na ich temat… bardzo przydatne. : D
gumuruh
4
-1 o ile wiem, samo FutureTask nie wystarcza do asynchronicznego uruchomienia czegokolwiek. Nadal musisz utworzyć wątek lub moduł wykonawczy, aby go uruchomić, jak w przykładzie Carlosa.
MikeFHay,
24

Nie podoba mi się pomysł wykorzystania do tego Reflection.
Nie tylko niebezpieczne, że brakuje go w niektórych refaktoryzacjach, ale można temu zaprzeczyć SecurityManager.

FutureTaskjest dobrą opcją, podobnie jak inne opcje z pakietu java.util.concurrent.
Mój ulubiony do prostych zadań:

    Executors.newSingleThreadExecutor().submit(task);

trochę krócej niż tworzenie wątku (zadanie jest wywoływalne lub uruchamialne)

user85421
źródło
1
Chodzi o to, że chcę tylko wykonać jedno wywołanie metody. W ten sposób musiałbym zmienić implementację klasy docelowej. To, czego chciałem, to dokładnie zadzwonić bez martwienia się o utrudnienie Runnable lub Callable
Felipe Hummel
to nie pomogłoby zbyt wiele :( ale normalnie wolałbym używać Runnable lub Callable zamiast Reflection
user85421
4
Krótka uwaga: przyjemniej jest śledzić ExecutorServiceinstancję, aby móc dzwonić, shutdown()kiedy trzeba.
Daniel Szalay
1
Gdybyś to zrobił, czy prawdopodobnie nie skończyłbyś z niezamkniętym ExecutorService, powodując, że Twoja JVM odmawia zamknięcia? Poleciłbym napisanie własnej metody, aby uzyskać jednowątkową, ograniczoną czasowo usługę ExecutorService.
djangofan
14

Możesz użyć składni Java8 dla CompletableFuture, w ten sposób możesz wykonać dodatkowe obliczenia asynchroniczne w oparciu o wynik wywołania funkcji asynchronicznej.

na przykład:

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

Więcej szczegółów można znaleźć w tym artykule

Tal Avissar
źródło
10

Możesz użyć @Asyncadnotacji z jcabi-Aspekty i AspectJ:

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

Kiedy wywołujesz save(), nowy wątek rozpoczyna się i wykonuje swoje ciało. Twój główny wątek jest kontynuowany bez czekania na wynik save().

yegor256
źródło
1
Zwróć uwagę, że takie podejście spowodowałoby, że wszystkie wywołania zapisywania byłyby asynchroniczne, co może, ale nie musi, być tym, czego chcemy. W przypadkach, gdy tylko niektóre wywołania danej metody powinny być asynchroniczne, można zastosować inne podejścia sugerowane na tej stronie.
bmorin
Czy mogę go używać w aplikacji internetowej (ponieważ zarządzanie wątkami nie jest tam zalecane)?
onlinenaman
8

Możesz do tego użyć 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);
}

Źródła: https://spring.io/guides/gs/async-method/

user3177227
źródło
10
jeśli używasz butów sprężynowych.
Giovanni Toraldo
6

Java zapewnia również przyjemny sposób wywoływania metod asynchronicznych. w java.util.concurrent mamy ExecutorService który pomaga w tym samym. Zainicjuj swój obiekt w ten sposób -

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

a następnie wywołaj funkcję taką jak-

asyncExecutor.execute(() -> {

                        TimeUnit.SECONDS.sleep(3L);}
Anand
źródło
3

Prawdopodobnie nie jest to prawdziwe rozwiązanie, ale teraz - w Javie 8 - możesz sprawić, by ten kod wyglądał przynajmniej trochę lepiej, używając wyrażenia lambda.

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

Możesz to nawet zrobić w jednej linii, nadal mając ją całkiem czytelną.

new Thread(() -> x.matches("something")).start();
kcpr
źródło
Naprawdę fajnie jest używać tego w Javie 8.
Mukti
3

Możesz użyć AsyncFuncod Cactoos :

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

Zostanie wykonany w tle, a wynik będzie dostępny get()jako plik Future.

yegor256
źródło
2

Nie jest to tak naprawdę powiązane, ale gdybym miał asynchronicznie wywołać metodę, np. Mecze (), użyłbym:

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

    });
}

Następnie do wywołania metody asynchronicznej użyłbym:

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

Przetestowałem to i działa. Pomyślałem, że może to pomóc innym, jeśli przyjdą po „metodę asynchroniczną”.

FlameBlazer
źródło
1

Jest też fajna biblioteka dla Async-Await stworzona przez EA: https://github.com/electronicarts/ea-async

Z ich pliku Readme:

Dzięki 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);
    }
}

Bez 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);
            });
    }
}
vitro
źródło