Como chamar de forma assíncrona um método em Java


126

Ultimamente, tenho visto as goroutines do Go e achei que seria bom ter algo semelhante em Java. Tanto quanto eu procurei a maneira comum de paralelizar uma chamada de método é fazer algo como:

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

Isso não é muito elegante. Existe um jeito melhor de fazer isso? Como eu precisava dessa solução em um projeto, decidi implementar minha própria classe de wrapper em torno de uma chamada de método assíncrona.

Publiquei minha classe de wrapper no J-Go . Mas não sei se é uma boa solução. O uso é simples:

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

ou menos detalhado:

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

Internamente, estou usando uma classe que implementa Runnable e faço algum trabalho de reflexão para obter o objeto de método correto e invocá-lo.

Eu quero uma opinião sobre minha pequena biblioteca e sobre o assunto de fazer chamadas de método assíncronas como esta em Java. É seguro? Já existe uma maneira mais simples?


1
Você pode mostrar seu código de J-Go lib novamente?
msangel

Eu estava trabalhando em um projeto em que estava lendo um fluxo de caracteres. Depois que uma palavra completa é lida, eu estava realizando muitas operações nessa palavra. Finalmente, coloquei-o em uma coleção. Depois que todos os dados do fluxo forem lidos, retornarei a resposta. Decidi iniciar o thread para cada vez que realizo uma operação em uma palavra. Mas diminuiu o desempenho geral. Então eu fiquei sabendo que os threads são em si uma operação cara. Não tenho certeza se o início de um thread para chamar um método simultaneamente pode dar um acréscimo de desempenho até que ele esteja executando qualquer operação pesada de E / S.
Amit Kumar Gupta

2
Ótima pergunta !! O Java 8 fornece CompletableFutures para isso. Outras respostas são baseados em versões provavelmente antigas do Java
programador

Respostas:


155

Acabei de descobrir que existe uma maneira mais limpa de fazer sua

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

(Pelo menos no Java 8), você pode usar uma expressão lambda para reduzi-la para:

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

Tão simples quanto criar uma função em JS!


Sua resposta ajudou no meu problema - stackoverflow.com/questions/27009448/… . Pouco complicado para aplicar o meu situatioin, mas trabalhou isso eventualmente :)
Deckard

Como chamar com parâmetros?
yatanadam

2
@yatanadam Isso pode responder à sua pergunta. Basta colocar o código acima dentro de um método e colar a variável como faria normalmente. Confira este código de teste que fiz para você.
user3004449

1
@eNnillaMS O thread precisa ser interrompido após a execução? Ele para automaticamente ou pelo coletor de lixo ?
user3004449

@ user3004449 A discussão para automaticamente, no entanto, você também pode forçá-la.
Shawn

58

O Java 8 introduziu o CompletableFuture disponível no pacote java.util.concurrent.CompletableFuture, pode ser usado para fazer uma chamada assíncrona:

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

CompletableFuturefoi mencionado em outra resposta, mas essa usou toda a supplyAsync(...)cadeia. Este é um invólucro simples que se encaixa perfeitamente na pergunta.
Ndm13 5/02

ForkJoinPool.commonPool().execute()tem um pouco menos despesas gerais
Mark Jeronimus 28/08

31

Você também pode considerar a classe java.util.concurrent.FutureTask.

Se você estiver usando o Java 5 ou posterior, o FutureTask é uma implementação chave na mão de "Uma computação assíncrona cancelável".

Existem comportamentos de agendamento de execução assíncrona ainda mais ricos disponíveis no java.util.concurrentpacote (por exemplo ScheduledExecutorService), mas FutureTaskpodem ter toda a funcionalidade necessária.

Eu chegaria ao ponto de dizer que não é mais aconselhável usar o primeiro padrão de código que você deu como exemplo desde que FutureTaskse tornou disponível. (Supondo que você esteja no Java 5 ou posterior.)


O problema é que eu só quero executar uma chamada de método. Dessa forma, eu teria que mudar a implementação da classe de destino. A coisa que eu queria é exatamente chamada sem ter que se preocupar sobre como implementar Runnable ou mobilizável
Felipe Hummel

Eu te escuto. Java ainda não possui funções de primeira classe, portanto este é o estado da arte no momento.
Shadit 04/12/2009

obrigado pelas palavras-chave 'future' ... agora estou abrindo os tutoriais sobre elas ... muito úteis. : D
gumuruh

4
-1, pelo que sei, o FutureTask por si só não é suficiente para executar nada de forma assíncrona. Você ainda precisa criar um Thread ou Executor para executá-lo, como no exemplo de Carlos.
MikeFHay

24

não gosto da ideia de usar o Reflection para isso.
Não é apenas perigoso por faltar em alguma refatoração, mas também pode ser negado SecurityManager.

FutureTaské uma boa opção, como as outras opções do pacote java.util.concurrent.
O meu favorito para tarefas simples:

    Executors.newSingleThreadExecutor().submit(task);

um pouco mais curto que a criação de um Thread (a tarefa é Callable ou Runnable)


1
O problema é que eu só quero executar uma chamada de método. Dessa forma, eu teria que mudar a implementação da classe de destino. A coisa que eu queria é exatamente chamada sem ter que se preocupar sobre como implementar Runnable ou mobilizável
Felipe Hummel

então esta ajuda Would'nt muito :( mas normalmente eu preferiria usar um Runnable ou mobilizável em vez de Reflexão
user85421

4
Apenas uma breve observação: é melhor acompanhar a ExecutorServiceinstância, para que você possa ligar shutdown()quando precisar.
Daniel Szalay

1
Se você fizesse isso, você não acabaria com o ExecutorService não fechado, fazendo com que sua JVM se recusasse a encerrar? Eu recomendaria escrever seu próprio método para obter um ExecutorService de thread único e com tempo limitado.
precisa saber é o seguinte

14

Você pode usar a sintaxe Java8 para CompletableFuture, desta forma, poderá executar cálculos assíncronos adicionais com base no resultado da chamada de uma função assíncrona.

por exemplo:

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

Mais detalhes podem ser encontrados neste artigo


10

Você pode usar a @Asyncanotação em jcabi-aspect e AspectJ:

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

Quando você chama save(), um novo thread inicia e executa seu corpo. Seu segmento principal continua sem aguardar o resultado de save().


1
Observe que essa abordagem faria todas as chamadas para salvar assíncronas, que podem ou não ser o que queremos. Nos casos em que apenas algumas chamadas para um determinado método devem ser feitas de forma assíncrona, outras abordagens sugeridas nesta página podem ser usadas.
bmorin

Posso usá-lo em um aplicativo Web (já que o gerenciamento de threads não é recomendado lá)?
Onlinenaman

8

Você pode usar o Future-AsyncResult para isso.

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

Referência: https://spring.io/guides/gs/async-method/


10
se você estiver usando boot de mola.
Giovanni Toraldo

6

Java também fornece uma boa maneira de chamar métodos assíncronos. em java.util.concurrent , temos ExecutorService que ajuda a fazer o mesmo. Inicialize seu objeto assim -

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

e depois chame a função como

asyncExecutor.execute(() -> {

                        TimeUnit.SECONDS.sleep(3L);}

3

Provavelmente não é uma solução real, mas agora - no Java 8 - você pode fazer esse código parecer um pouco melhor usando a expressão lambda.

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

E você pode até fazer isso em uma linha, mantendo-a bem legível.

new Thread(() -> x.matches("something")).start();

É muito bom para usar esta usando Java 8.
Mukti

3

Você pode usar AsyncFuncdo Cactoos :

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

Ele será executado em segundo plano e o resultado estará disponível em get()a Future.


2

Isso não está realmente relacionado, mas se eu chamasse um método de forma assíncrona, por exemplo, match (), usaria:

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

    });
}

Então, para chamar o método assíncrono, eu usaria:

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

Eu testei isso e funciona. Apenas pensei que poderia ajudar os outros se eles viessem para o "método assíncrono".


1

Também existe uma biblioteca agradável para o Async-Await criada pela EA: https://github.com/electronicarts/ea-async

Do Leiame:

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

Sem 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);
            });
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.