Cara memanggil metode secara asinkron di Java

127

Saya telah melihat goroutine Go akhir-akhir ini dan berpikir akan menyenangkan memiliki sesuatu yang serupa di Java. Sejauh saya telah mencari cara umum untuk memparalelkan pemanggilan metode adalah dengan melakukan sesuatu seperti:

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

Itu tidak terlalu elegan. Apakah ada cara yang lebih baik untuk melakukan ini? Saya membutuhkan solusi seperti itu dalam sebuah proyek jadi saya memutuskan untuk mengimplementasikan kelas pembungkus saya sendiri di sekitar pemanggilan metode async.

Saya menerbitkan kelas pembungkus saya di J-Go . Tapi saya tidak tahu apakah ini solusi yang bagus. Penggunaannya sederhana:

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

atau kurang bertele-tele:

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

Secara internal, saya menggunakan kelas yang mengimplementasikan Runnable dan melakukan beberapa pekerjaan Refleksi untuk mendapatkan objek metode yang benar dan menjalankannya.

Saya ingin pendapat tentang perpustakaan kecil saya dan tentang masalah membuat panggilan metode asinkron seperti ini di Java. Apakah ini aman? Apakah sudah ada cara yang lebih sederhana?

Felipe Hummel
sumber
1
Bisakah Anda menunjukkan kode J-Go lib Anda lagi?
msangel
Saya sedang mengerjakan proyek di mana saya membaca karakter aliran sungai. Setelah satu kata lengkap dibaca, saya melakukan banyak operasi pada kata itu. Akhirnya saya memasukkannya ke dalam koleksi. Setelah semua data dari aliran dibaca, saya mengembalikan responsnya. Saya memutuskan untuk memulai utas setiap kali saya melakukan operasi pada sebuah kata. Tapi itu menurunkan performa secara keseluruhan. Kemudian saya mengetahui bahwa utas itu sendiri adalah operasi yang mahal. Saya tidak yakin apakah memulai utas untuk memanggil metode secara bersamaan dapat memberikan peningkatan kinerja apa pun hingga melakukan operasi IO yang berat.
Amit Kumar Gupta
2
Pertanyaan bagus !! Java 8 menyediakan CompletableFutures untuk ini. Jawaban lain didasarkan pada kemungkinan versi lama Java
TriCore

Jawaban:

155

Saya baru saja menemukan bahwa ada cara yang lebih bersih untuk melakukan Anda

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

(Setidaknya di Java 8), Anda dapat menggunakan ekspresi lambda untuk mempersingkatnya menjadi:

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

Sesederhana membuat fungsi di JS!

AegisHexad
sumber
Jawaban Anda membantu masalah saya - stackoverflow.com/questions/27009448/… . Sedikit rumit untuk menerapkan situasi saya, tetapi akhirnya berhasil :)
Deckard
Bagaimana cara memanggil dengan parameter?
yatanadam
2
@yatanadam Ini mungkin menjawab pertanyaan Anda. Cukup tempatkan kode di atas di dalam metode dan masukkan variabel seperti yang biasa Anda lakukan. Lihat kode tes yang saya buat untuk Anda.
pengguna3004449
1
@eNnillaMS Apakah utas harus dihentikan setelah berjalan? Apakah itu berhenti secara otomatis, atau oleh pengumpul sampah ?
pengguna3004449
@ user3004449 Utas berhenti secara otomatis, namun, Anda juga dapat memaksanya.
Shawn
59

Java 8 memperkenalkan CompletableFuture yang tersedia dalam paket java.util.concurrent.CompletableFuture, dapat digunakan untuk membuat panggilan asynch:

CompletableFuture.runAsync(() -> {
    // method call or code to be asynch.
});
Rahul Chauhan
sumber
CompletableFuturedisebutkan dalam jawaban lain, tetapi jawaban itu menggunakan seluruh supplyAsync(...)rantai. Ini adalah pembungkus sederhana yang cocok dengan pertanyaan dengan sempurna.
ndm13
ForkJoinPool.commonPool().execute()memiliki overhead yang sedikit lebih sedikit
Mark Jeronimus
31

Anda mungkin juga ingin mempertimbangkan kelas tersebut java.util.concurrent.FutureTask.

Jika Anda menggunakan Java 5 atau yang lebih baru, FutureTask adalah implementasi siap pakai dari "Komputasi asinkron yang dapat dibatalkan".

Bahkan ada lebih banyak perilaku penjadwalan eksekusi asinkron yang tersedia dalam java.util.concurrentpaket (misalnya, ScheduledExecutorService), tetapi FutureTaskmungkin memiliki semua fungsionalitas yang Anda butuhkan.

Saya bahkan akan melangkah lebih jauh dengan mengatakan bahwa tidak lagi disarankan untuk menggunakan pola kode pertama yang Anda berikan sebagai contoh sejak FutureTasktersedia. (Dengan asumsi Anda menggunakan Java 5 atau lebih baru.)

shadit
sumber
Masalahnya adalah saya hanya ingin menjalankan satu pemanggilan metode. Dengan cara ini saya harus mengubah implementasi kelas target. Hal yang saya inginkan adalah menelepon secara tepat tanpa harus khawatir tentang penerapan Runnable atau Callable
Felipe Hummel
Aku mendengarmu. Java tidak (belum) memiliki fungsi kelas satu, jadi inilah yang paling mutakhir sekarang.
shadit
terima kasih untuk kata kunci 'masa depan' ... sekarang saya membuka tutorial tentang mereka ... sangat berguna. : D
gumuruh
4
-1 sejauh yang saya tahu, FutureTask dengan sendirinya tidak cukup untuk menjalankan apa pun secara asinkron. Anda masih perlu membuat Thread atau Executor untuk menjalankannya, seperti pada contoh Carlos.
MikeFHay
24

saya tidak suka ide menggunakan Refleksi untuk itu.
Tidak hanya berbahaya jika terlewat di beberapa refactoring, tapi juga bisa diingkari SecurityManager.

FutureTaskadalah opsi yang bagus sebagai opsi lain dari paket java.util.concurrent.
Favorit saya untuk tugas-tugas sederhana:

    Executors.newSingleThreadExecutor().submit(task);

sedikit lebih pendek daripada membuat Thread (tugas adalah Callable atau Runnable)

pengguna85421
sumber
1
Masalahnya adalah saya hanya ingin menjalankan satu pemanggilan metode. Dengan cara ini saya harus mengubah implementasi kelas target. Hal yang saya inginkan adalah tepat menelepon tanpa harus khawatir tentang penerapan Runnable atau Callable
Felipe Hummel
maka ini tidak akan banyak membantu :( tetapi biasanya saya lebih suka menggunakan Runnable atau Callable daripada Refleksi
user85421
4
Hanya catatan singkat: Lebih baik melacak ExecutorServiceinstance, sehingga Anda dapat menelepon shutdown()saat diperlukan.
Daniel Szalay
1
Jika Anda melakukan ini, bukankah Anda akan berakhir dengan ExecutorService yang tidak ditutup, menyebabkan JVM Anda menolak untuk dimatikan? Saya akan merekomendasikan menulis metode Anda sendiri untuk mendapatkan ExecutorService single-threaded, waktu terbatas.
djangofan
14

Anda dapat menggunakan sintaks Java8 untuk CompletableFuture, dengan cara ini Anda dapat melakukan komputasi asinkron tambahan berdasarkan hasil dari pemanggilan fungsi asinkron.

sebagai contoh:

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

Rincian lebih lanjut dapat ditemukan di artikel ini

Tal Avissar
sumber
10

Anda dapat menggunakan @Asyncanotasi dari jcabi-aspect dan AspectJ:

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

Saat Anda memanggil save(), utas baru dimulai dan menjalankan tubuhnya. Untaian utama Anda berlanjut tanpa menunggu hasil save().

yegor256
sumber
1
Perhatikan bahwa pendekatan ini akan membuat semua panggilan untuk menyimpan asynchronous, yang mungkin atau mungkin bukan yang kita inginkan. Jika hanya beberapa panggilan ke metode tertentu yang dibuat asinkron, pendekatan lain yang disarankan di halaman ini dapat digunakan.
bmorin
Dapatkah saya menggunakannya dalam aplikasi web (karena mengelola utas tidak disarankan di sana)?
onlinenaman
8

Anda dapat menggunakan Future-AsyncResult untuk ini.

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

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

pengguna3177227
sumber
10
jika Anda menggunakan pegas-boot.
Giovanni Toraldo
6

Java juga menyediakan cara yang bagus untuk memanggil metode async. di java.util.concurrent kami memiliki ExecutorService yang membantu dalam melakukan hal yang sama. Inisialisasi objek Anda seperti ini -

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

dan kemudian memanggil fungsi seperti-

asyncExecutor.execute(() -> {

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

Ini mungkin bukan solusi nyata, tetapi sekarang - di Java 8 - Anda dapat membuat kode ini terlihat setidaknya sedikit lebih baik menggunakan ekspresi lambda.

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

Dan Anda bahkan dapat melakukan ini dalam satu baris, tetap membuatnya cukup mudah dibaca.

new Thread(() -> x.matches("something")).start();
kcpr
sumber
Sangat menyenangkan menggunakan ini menggunakan Java 8.
Mukti
3

Anda dapat menggunakan AsyncFuncdari Cactoos :

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

Ini akan dijalankan di latar belakang dan hasilnya akan tersedia dalam get()format Future.

yegor256
sumber
2

Ini tidak benar-benar terkait tetapi jika saya memanggil metode secara asinkron misalnya match (), saya akan menggunakan:

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

    });
}

Kemudian untuk memanggil metode asynchronous saya akan menggunakan:

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

Saya telah menguji ini dan berhasil. Hanya berpikir itu dapat membantu orang lain jika mereka datang untuk "metode asynchronous".

FlameBlazer
sumber
1

Ada juga perpustakaan bagus untuk Async-Await yang dibuat oleh EA: https://github.com/electronicarts/ea-async

Dari Readme mereka:

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

Tanpa 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
sumber