Java'da bir yöntemi eşzamansız olarak çağırma


128

Son zamanlarda Go'nun gorutinlerine bakıyordum ve Java'da benzer bir şeye sahip olmanın güzel olacağını düşündüm. Bir yöntem çağrısını paralel hale getirmenin en yaygın yolu araştırdığım kadarıyla şuna benzer bir şey yapmaktır:

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

Bu çok zarif değil. Bunu yapmanın daha iyi bir yolu var mı? Bir projede böyle bir çözüme ihtiyacım vardı, bu yüzden zaman uyumsuz bir yöntem çağrısı etrafında kendi sarmalayıcı sınıfımı uygulamaya karar verdim.

Paketleme sınıfımı J-Go'da yayınladım . Ama bunun iyi bir çözüm olup olmadığını bilmiyorum. Kullanım basittir:

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

veya daha az ayrıntılı:

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

Dahili olarak, Runnable uygulayan bir sınıf kullanıyorum ve doğru yöntem nesnesini almak ve onu çağırmak için bazı Yansıtma çalışmaları yapıyorum.

Küçük kütüphanem ve Java'da bu gibi asenkron yöntem çağrıları yapma konusunda biraz fikir istiyorum. Güvenli mi? Zaten daha basit bir yol var mı?


1
J-Go lib kodunu tekrar gösterebilir misin?
msangel

Bir akarsuyu okuduğum bir proje üzerinde çalışıyordum. Tam bir kelime okunduktan sonra o kelime üzerinde birçok işlem yapıyordum. Sonunda bir koleksiyona koydum. Akıştaki tüm veriler okunduktan sonra yanıtı döndürüyorum. Bir kelime üzerinde her işlem yaptığımda iş parçacığı başlatmaya karar verdim. Ancak genel performansı düşürdü. O zaman iş parçacıklarının kendi içinde pahalı bir işlem olduğunu anladım. Bir yöntemi eşzamanlı olarak çağırmak için bir iş parçacığı başlatmanın, herhangi bir ağır GÇ işlemi gerçekleştirene kadar herhangi bir performans artışı sağlayıp sağlamayacağından emin değilim.
Amit Kumar Gupta

2
Harika soru !! Java 8 bunun için CompletableFutures sağlar. Diğer yanıtlar muhtemelen Java'nın eski sürümlerine dayanmaktadır
TriCore

Yanıtlar:


156

Daha temiz bir yol olduğunu keşfettim.

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

(En azından Java 8'de), lambda ifadesini şu şekilde kısaltmak için kullanabilirsiniz:

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

JS'de bir işlev yapmak kadar basit!


Cevabınız sorunuma yardımcı oldu - stackoverflow.com/questions/27009448/… . Durumumu uygulamak biraz zor, ama sonunda sonuçlandı :)
Deckard

Parametrelerle nasıl çağrılır?
yatanadam

2
@yatanadam Sorunuzun cevabı bu olabilir. Yukarıdaki kodu bir yöntemin içine yerleştirin ve normalde yaptığınız gibi değişkeni geçin. Sizin için yaptığım bu test koduna bakın.
user3004449

1
@eNnillaMS İş parçacığı çalıştırıldıktan sonra durdurulmalı mı? Otomatik olarak mı yoksa çöp toplayıcı tarafından mı durur ?
user3004449

@ user3004449 İş parçacığı otomatik olarak durur, ancak siz de onu zorlayabilirsiniz.
Shawn

59

Java 8, java.util.concurrent.CompletableFuture paketinde bulunan CompletableFuture'u kullanıma sundu, eşzamansız çağrı yapmak için kullanılabilir:

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

CompletableFuturebaşka bir cevapta bahsedildi, ancak bu tüm supplyAsync(...)zinciri kullandı. Bu, soruya mükemmel şekilde uyan basit bir paketleyicidir.
ndm13

ForkJoinPool.commonPool().execute()genel gider biraz daha az
Mark Jeronimus

31

Sınıfı da düşünmek isteyebilirsiniz java.util.concurrent.FutureTask.

Java 5 veya sonraki bir sürümünü kullanıyorsanız FutureTask , "İptal edilebilir bir eşzamansız hesaplama" nın anahtar teslimi bir uygulamasıdır.

java.util.concurrentPakette daha zengin eşzamansız yürütme planlama davranışları da vardır (örneğin ScheduledExecutorService), ancak FutureTaskihtiyaç duyduğunuz tüm işlevlere sahip olabilir.

Hatta o zamandan beri örnek olarak verdiğiniz ilk kod modelini kullanmanın artık tavsiye edilmediğini söyleyecek kadar ileri gideceğim FutureTask. (Java 5 veya sonraki bir sürümde olduğunuzu varsayarak.)


Mesele şu ki, sadece bir yöntem çağrısı yürütmek istiyorum. Bu şekilde hedef sınıfın uygulamasını değiştirmem gerekirdi. İstediğim şey, Runnable veya Callable'ı büyütme konusunda endişelenmeden tam olarak aramak
Felipe Hummel

Seni duyuyorum. Java (henüz) birinci sınıf işlevlere sahip değildir, bu nedenle şu anda en son teknoloji budur.
shadit

'gelecek' anahtar kelimeler için teşekkürler ... şimdi onlar hakkında öğreticileri açıyorum ... çok faydalı. : D
gumuruh

4
-1 anlayabildiğim kadarıyla FutureTask tek başına herhangi bir şeyi eşzamansız olarak çalıştırmak için yeterli değil. Carlos'un örneğinde olduğu gibi, çalıştırmak için hala bir İş Parçacığı veya Yürütücü oluşturmanız gerekir.
MikeFHay

24

Bunun için Yansıma kullanma fikrinden hoşlanmıyorum.
Sadece bazı yeniden düzenlemelerde onu kaçırmak tehlikeli değil, aynı zamanda reddedilebilir SecurityManager.

FutureTaskjava.util.concurrent paketindeki diğer seçenekler gibi iyi bir seçenektir.
Basit görevler için favorim:

    Executors.newSingleThreadExecutor().submit(task);

İş Parçacığı oluşturmaktan biraz daha kısa (görev Çağrılabilir veya Çalıştırılabilirdir)


1
Mesele şu ki, sadece bir yöntem çağrısı yürütmek istiyorum. Bu şekilde hedef sınıfın uygulamasını değiştirmem gerekirdi. İstediğim şey, Runnable veya Callable'ı iyileştirme konusunda endişelenmek zorunda kalmadan tam olarak aramak
Felipe Hummel

o zaman bu pek yardımcı olmazdı :( ama normalde Yansıma yerine
Çalıştırılabilir

4
Kısa bir not: Örneği takip etmek daha hoş ExecutorService, böylece shutdown()gerektiğinde arayabilirsin .
Daniel Szalay

1
Bunu yaptıysanız, kapatılmamış ExecutorService ile sonuçlanamaz ve JVM'nizin kapatmayı reddetmesine neden olmaz mıydınız? Tek iş parçacıklı, zaman sınırlı bir ExecutorService almak için kendi yönteminizi yazmanızı tavsiye ederim.
djangofan

14

CompletableFuture için Java8 sözdizimini kullanabilirsiniz, bu şekilde zaman uyumsuz bir işlevi çağırmanın sonucuna göre ek eşzamansız hesaplamalar gerçekleştirebilirsiniz.

Örneğin:

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

Bu makalede daha fazla ayrıntı bulunabilir


10

Jcabi-yönlerinden ve AspectJ'den@Async ek açıklamayı kullanabilirsiniz :

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

Aradığınızda save(), yeni bir iş parçacığı başlar ve gövdesini yürütür. Ana iş parçacığınız sonucunu beklemeden devam ediyor save().


1
Bu yaklaşımın, istediğimiz gibi olabilecek veya olmayabilecek tüm çağrıları eşzamansız kaydetmek için yapacağını unutmayın. Belirli bir yönteme yalnızca bazı çağrıların eşzamansız yapılması gerektiği durumlarda, bu sayfada önerilen diğer yaklaşımlar kullanılabilir.
bmorin

Bunu bir web uygulamasında kullanabilir miyim (burada iş parçacıklarının yönetilmesi önerilmediği için)?
onlinenaman

8

Bunun için Future-AsyncResult'u kullanabilirsiniz.

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

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


10
yaylı önyükleme kullanıyorsanız.
Giovanni Toraldo

6

Java, zaman uyumsuz yöntemleri çağırmanın güzel bir yolunu da sağlar. içinde java.util.concurrent Elimizdeki ExecutorService aynı şeyi yardımcı olur. Nesnenizi şu şekilde başlatın -

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

ve sonra şu işlevi çağırın:

asyncExecutor.execute(() -> {

                        TimeUnit.SECONDS.sleep(3L);}

3

Muhtemelen gerçek bir çözüm değil, ama şimdi - Java 8'de - bu kodu lambda ifadesini kullanarak en azından biraz daha iyi gösterebilirsiniz.

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

Ve bunu tek satırda bile yapabilirsiniz, yine de oldukça okunaklı.

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

Bunu Java 8 kullanarak kullanmak gerçekten çok güzel.
Mukti

3

Sen kullanabilirsiniz AsyncFuncgelen Cactoos :

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

Arka planda yürütülecek ve sonuç get()olarak bir Future.


2

Bu gerçekten alakalı değil, ancak eşzamansız olarak bir yöntemi çağıracak olsaydım, örneğin match (), şunu kullanırdım:

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

    });
}

Daha sonra asenkron yöntemi çağırmak için kullanacağım:

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

Bunu test ettim ve işe yarıyor. Sadece "asenkron yöntem" için geldiklerinde başkalarına yardımcı olabileceğini düşündüm.


1

EA tarafından oluşturulan Async-Await için de güzel bir kütüphane var: https://github.com/electronicarts/ea-async

Readme'larından:

EA Async ile

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 olmadan

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);
            });
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.