Cómo llamar asincrónicamente a un método en Java

127

Últimamente he estado mirando goroutines de Go y pensé que sería bueno tener algo similar en Java. Por lo que he buscado, la forma común de paralelizar una llamada a un método es hacer algo como:

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

Eso no es muy elegante. ¿Existe una mejor manera de hacer esto? Necesitaba una solución de este tipo en un proyecto, así que decidí implementar mi propia clase contenedora en torno a una llamada al método asíncrono.

Publiqué mi clase contenedora en J-Go . Pero no sé si es una buena solución. El uso es simple:

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

o menos detallado:

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

Internamente, estoy usando una clase que implementa Runnable y hago un trabajo de reflexión para obtener el objeto de método correcto e invocarlo.

Quiero una opinión sobre mi pequeña biblioteca y sobre el tema de hacer llamadas a métodos asíncronos como esta en Java. ¿Es seguro? ¿Existe ya una forma más sencilla?

Felipe Hummel
fuente
1
¿Puedes volver a mostrar tu código de J-Go lib?
msangel
Estaba trabajando en un proyecto en el que estaba leyendo una secuencia de caracteres. Una vez que se lee una palabra completa, estaba realizando muchas operaciones en esa palabra. Finalmente lo puse en una colección. Una vez que se leen todos los datos de la transmisión, devuelvo la respuesta. Decidí iniciar el hilo por cada vez que realice una operación en una palabra. Pero disminuyó el rendimiento general. Entonces llegué a saber que los hilos son en sí mismos una operación costosa. No estoy seguro de si iniciar un subproceso para llamar a un método al mismo tiempo puede proporcionar un rendimiento adicional hasta que esté realizando una operación de E / S pesada.
Amit Kumar Gupta
2
Gran pregunta !! Java 8 proporciona CompletableFutures para esto. Otras respuestas se basan en probablemente versiones antiguas de Java
TriCore

Respuestas:

155

Acabo de descubrir que hay una forma más limpia de hacer tu

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

(Al menos en Java 8), puede usar una expresión lambda para acortarla a:

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

¡Tan simple como hacer una función en JS!

AegisHexad
fuente
Su respuesta ayudó a mi problema - stackoverflow.com/questions/27009448/… . Un poco complicado de aplicar mi situación, pero finalmente lo resolví :)
Deckard
¿Cómo llamar con parámetros?
yatanadam
2
@yatanadam Esto puede responder a su pregunta. Simplemente coloque el código anterior dentro de un método y pase la variable como lo haría normalmente. Mira este código de prueba que hice para ti.
user3004449
1
@eNnillaMS ¿Es necesario detener el hilo después de ejecutarse? ¿Se detiene automáticamente o por el recolector de basura ?
user3004449
@ user3004449 El hilo se detiene automáticamente, sin embargo, también puede forzarlo.
Shawn
59

Java 8 introdujo CompletableFuture disponible en el paquete java.util.concurrent.CompletableFuture, se puede utilizar para realizar una llamada asíncrona:

CompletableFuture.runAsync(() -> {
    // method call or code to be asynch.
});
Rahul Chauhan
fuente
CompletableFuturese mencionó en otra respuesta, pero esa usó toda la supplyAsync(...)cadena. Este es un envoltorio simple que se ajusta perfectamente a la pregunta.
ndm13
ForkJoinPool.commonPool().execute()tiene un poco menos de gastos generales
Mark Jeronimus
31

Es posible que desee considerar también la clase java.util.concurrent.FutureTask.

Si está utilizando Java 5 o posterior, FutureTask es una implementación llave en mano de "Un cálculo asincrónico cancelable ".

Hay comportamientos de programación de ejecución asincrónica aún más ricos disponibles en el java.util.concurrentpaquete (por ejemplo, ScheduledExecutorService), pero FutureTaskpueden tener toda la funcionalidad que necesita.

Incluso iría tan lejos como para decir que ya no es recomendable utilizar el primer patrón de código que dio como ejemplo desde que FutureTaskestuvo disponible. (Suponiendo que esté en Java 5 o posterior).

shadit
fuente
La cosa es que solo quiero ejecutar una llamada a un método. De esta manera tendría que cambiar la implementación de la clase objetivo. Lo que quería es exactamente llamar sin tener que preocuparme por afectar Runnable o Callable
Felipe Hummel
Te escucho. Java no tiene (todavía) funciones de primera clase, por lo que este es el estado del arte en este momento.
Shadit
gracias por las palabras clave de 'futuro' ... ahora estoy abriendo los tutoriales sobre ellas ... muy útiles. : D
gumuruh
4
-1 por lo que puedo decir, FutureTask por sí solo no es suficiente para ejecutar nada de forma asincrónica. Aún necesita crear un Thread o Executor para ejecutarlo, como en el ejemplo de Carlos.
MikeFHay
24

No me gusta la idea de usar Reflection para eso.
No solo es peligroso por perderlo en alguna refactorización, sino que también puede ser negado por SecurityManager.

FutureTaskes una buena opción como las otras opciones del paquete java.util.concurrent.
Mi favorito para tareas simples:

    Executors.newSingleThreadExecutor().submit(task);

un poco más corto que crear un hilo (la tarea es invocable o ejecutable)

usuario85421
fuente
1
La cosa es que solo quiero ejecutar una llamada a un método. De esta manera tendría que cambiar la implementación de la clase objetivo. Lo que quería es exactamente llamar sin tener que preocuparme por la imposición de Runnable o Callable
Felipe Hummel
entonces esto no ayudaría mucho :( pero normalmente preferiría usar un Runnable o Callable en lugar de Reflection
user85421
4
Solo una breve nota: es mejor realizar un seguimiento de la ExecutorServiceinstancia, para que pueda llamar shutdown()cuando sea necesario.
Daniel Szalay
1
Si hiciera esto, ¿no terminaría posiblemente con ExecutorService sin cerrar, haciendo que su JVM se niegue a apagarse? Recomendaría escribir su propio método para obtener un ExecutorService de un solo subproceso y por tiempo limitado.
djangofan
14

Puede usar la sintaxis Java8 para CompletableFuture, de esta manera puede realizar cálculos asíncronos adicionales basados ​​en el resultado de llamar a una función asíncrona.

por ejemplo:

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

Se pueden encontrar más detalles en este artículo.

Tal Avissar
fuente
10

Puede utilizar la @Asyncanotación de jcabi-aspectos y AspectJ:

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

Cuando llamas save(), se inicia un nuevo hilo y ejecuta su cuerpo. Tu hilo principal continúa sin esperar el resultado de save().

yegor256
fuente
1
Tenga en cuenta que este enfoque haría que todas las llamadas a guardar sean asincrónicas, lo que podría ser o no lo que queremos. En los casos en los que solo algunas llamadas a un método determinado deban hacerse asincrónicas, se pueden utilizar otros enfoques sugeridos en esta página.
bmorin
¿Puedo usarlo en una aplicación web (ya que allí no se recomienda administrar hilos)?
onlinenaman
8

Puede usar Future-AsyncResult para esto.

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

Referencia: https://spring.io/guides/gs/async-method/

usuario3177227
fuente
10
si está utilizando spring-boot.
Giovanni Toraldo
6

Java también proporciona una buena forma de llamar a métodos asíncronos. en java.util.concurrent tenemos ExecutorService que ayuda a hacer lo mismo. Inicializa tu objeto así:

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

y luego llame a la función como-

asyncExecutor.execute(() -> {

                        TimeUnit.SECONDS.sleep(3L);}
Anand
fuente
3

Probablemente no sea una solución real, pero ahora, en Java 8, puede hacer que este código se vea al menos un poco mejor usando la expresión lambda.

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

E incluso podría hacer esto en una línea, aún teniendo bastante legibilidad.

new Thread(() -> x.matches("something")).start();
kcpr
fuente
Es muy agradable de utilizar esta usando Java 8.
Mukti
3

Puedes usar AsyncFuncde Cactoos :

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

Se ejecutará en segundo plano y el resultado estará disponible en get()formato Future.

yegor256
fuente
2

Esto no está realmente relacionado, pero si tuviera que llamar de forma asincrónica a un método, por ejemplo, coincide con (), usaría:

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

    });
}

Luego, para llamar al método asincrónico, usaría:

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

He probado esto y funciona. Pensé que podría ayudar a otros si simplemente vinieran por el "método asincrónico".

FlameBlazer
fuente
1

También hay una buena biblioteca para Async-Await creada por EA: https://github.com/electronicarts/ea-async

De su archivo Léame:

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

Sin 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
fuente