วิธีการเรียกใช้เมธอดแบบอะซิงโครนัสใน Java


128

เมื่อเร็ว ๆ นี้ฉันได้ดู goroutines ของ Goและคิดว่าคงจะดีถ้ามีอะไรที่คล้ายกันใน Java เท่าที่ฉันได้ค้นหาวิธีทั่วไปในการขนานการเรียกเมธอดคือทำสิ่งต่างๆเช่น:

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

นั่นไม่สง่างามมาก มีวิธีที่ดีกว่านี้หรือไม่? ฉันต้องการโซลูชันดังกล่าวในโปรเจ็กต์ดังนั้นฉันจึงตัดสินใจใช้คลาส wrapper ของฉันเองโดยใช้การเรียกเมธอด async

ฉันตีพิมพ์ระดับกระดาษห่อของฉันในJ-Go แต่ฉันไม่รู้ว่ามันเป็นทางออกที่ดีหรือเปล่า การใช้งานนั้นง่ายมาก:

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

หรือน้อยกว่า verbose:

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

ภายในฉันกำลังใช้คลาสที่ใช้ Runnable และทำงาน Reflection บางอย่างเพื่อรับวัตถุวิธีการที่ถูกต้องและเรียกใช้

ฉันต้องการความคิดเห็นเกี่ยวกับไลบรารีเล็ก ๆ ของฉันและในเรื่องของการสร้างวิธีการ async เรียกแบบนี้ใน Java ปลอดภัยจริงหรือ? มีวิธีที่ง่ายกว่านี้อยู่แล้วหรือไม่?


1
คุณสามารถแสดงรหัส J-Go lib อีกครั้งได้หรือไม่?
msangel

ฉันกำลังทำโครงการที่ฉันกำลังอ่านสตรีมตัวละครที่ชาญฉลาด เมื่ออ่านคำสมบูรณ์แล้วฉันกำลังดำเนินการหลายอย่างกับคำนั้น ในที่สุดฉันก็เอามาเป็นคอลเลกชั่น เมื่ออ่านข้อมูลทั้งหมดจากสตรีมแล้วฉันจะตอบกลับ ฉันตัดสินใจเริ่มเธรดทุกครั้งที่ดำเนินการกับคำ แต่ประสิทธิภาพโดยรวมลดลง จากนั้นฉันก็รู้ว่าเธรดอยู่ในตัวเองเป็นการดำเนินการที่มีราคาแพง ฉันไม่แน่ใจว่าการเริ่มเธรดเพื่อเรียกใช้เมธอดพร้อมกันสามารถเพิ่มประสิทธิภาพใด ๆ ได้หรือไม่จนกว่าจะดำเนินการ IO หนัก ๆ
Amit Kumar Gupta

2
คำถามเด็ด !! Java 8 จัดเตรียม CompletableFutures สำหรับสิ่งนี้ คำตอบอื่น ๆ อ้างอิงจาก Java เวอร์ชันเก่า
TriCore

คำตอบ:


156

ฉันเพิ่งค้นพบว่ามีวิธีที่สะอาดกว่าในการทำไฟล์

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

(อย่างน้อยใน Java 8) คุณสามารถใช้นิพจน์แลมบ์ดาเพื่อย่อให้เป็น:

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

ง่ายเหมือนการสร้างฟังก์ชันใน JS!


คำตอบของคุณช่วยให้ปัญหาของฉัน - stackoverflow.com/questions/27009448/... ยุ่งยากเล็กน้อยในการใช้ situatioin ของฉัน แต่ได้ผลในที่สุด :)
Deckard

จะโทรด้วยพารามิเตอร์อย่างไร?
yatanadam

2
@yatanadam สิ่งนี้อาจตอบคำถามของคุณได้ เพียงวางโค้ดด้านบนไว้ในเมธอดและวางตัวแปรเหมือนที่คุณทำตามปกติ ตรวจสอบรหัสทดสอบที่ฉันสร้างขึ้นเพื่อคุณ
user3004449

1
@eNnillaMS เธรดต้องหยุดหลังจากรันหรือไม่? มันหยุดโดยอัตโนมัติหรือโดยคนเก็บขยะ ?
user3004449

@ user3004449 เธรดจะหยุดโดยอัตโนมัติอย่างไรก็ตามคุณสามารถบังคับได้เช่นกัน
Shawn

59

Java 8 แนะนำ CompletableFuture ที่มีอยู่ในแพ็คเกจ java.util.concurrent.CompletableFuture สามารถใช้เพื่อโทรแบบ asynch:

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

CompletableFutureถูกพูดถึงในคำตอบอื่น แต่คำตอบนั้นใช้supplyAsync(...)โซ่ทั้งหมด นี่คือกระดาษห่อแบบเรียบง่ายที่เหมาะกับคำถามอย่างสมบูรณ์แบบ
ndm13

ForkJoinPool.commonPool().execute()มีค่าใช้จ่ายน้อยกว่าเล็กน้อย
Mark Jeronimus

31

คุณอาจต้องการพิจารณาชั้นเรียนjava.util.concurrent.FutureTaskด้วย

หากคุณใช้ Java 5 หรือใหม่กว่าFutureTaskเป็นการใช้งาน "A cancellable asynchronous computation" แบบครบวงจร

มีพฤติกรรมการจัดกำหนดการการดำเนินการแบบอะซิงโครนัสที่สมบูรณ์ยิ่งขึ้นในjava.util.concurrentแพ็กเกจ (เช่นScheduledExecutorService) แต่FutureTaskอาจมีฟังก์ชันทั้งหมดที่คุณต้องการ

ฉันอยากจะบอกว่าไม่แนะนำให้ใช้รูปแบบรหัสแรกที่คุณให้เป็นตัวอย่างอีกต่อไปตั้งแต่นั้นFutureTaskมา (สมมติว่าคุณใช้ Java 5 หรือใหม่กว่า)


สิ่งนี้คือฉันแค่ต้องการเรียกใช้เมธอดเดียว วิธีนี้ฉันจะต้องเปลี่ยนการใช้คลาสเป้าหมาย สิ่งที่ฉันต้องการคือการโทรโดยไม่ต้องกังวลเกี่ยวกับการขัดขวางการเรียกใช้หรือเรียกได้
Felipe Hummel

ฉันได้ยินคุณ. Java ยังไม่มีฟังก์ชันระดับเฟิร์สคลาสดังนั้นนี่จึงเป็นสิ่งที่ล้ำสมัยในตอนนี้
เฉดสี

ขอบคุณสำหรับคำหลัก 'อนาคต' ... ตอนนี้ฉันกำลังเปิดบทเรียนเกี่ยวกับพวกเขา ... มีประโยชน์มาก : D
gumuruh

4
-1 เท่าที่ฉันบอกได้ FutureTask ด้วยตัวเองไม่เพียงพอที่จะเรียกใช้อะไรแบบอะซิงโครนัส คุณยังคงต้องสร้างเธรดหรือตัวดำเนินการเพื่อเรียกใช้ดังตัวอย่างของ Carlos
MikeFHay

24

ฉันไม่ชอบแนวคิดในการใช้ Reflection เพื่อสิ่งนั้น
ไม่เพียง แต่เป็นอันตรายสำหรับมันหายไปใน refactoring บางอย่าง SecurityManagerแต่ก็ยังสามารถปฏิเสธโดย

FutureTaskเป็นตัวเลือกที่ดีเช่นเดียวกับตัวเลือกอื่น ๆ จากแพ็คเกจ java.util.concurrent
สิ่งที่ฉันชอบสำหรับงานง่ายๆ:

    Executors.newSingleThreadExecutor().submit(task);

สั้นกว่าการสร้าง Thread เล็กน้อย (งานคือ Callable หรือ Runnable)


1
สิ่งนี้คือฉันแค่ต้องการเรียกใช้เมธอดเดียว วิธีนี้ฉันจะต้องเปลี่ยนการใช้คลาสเป้าหมาย สิ่งที่ฉันต้องการคือการโทรโดยไม่ต้องกังวลเกี่ยวกับการขัดขวางการเรียกใช้หรือเรียกได้
เฟลิเป้ฮุมเมล

ถ้าอย่างนั้นสิ่งนี้จะช่วยไม่ได้มากนัก :( แต่โดยปกติฉันต้องการใช้ Runnable หรือ Callable แทน Reflection
user85421

4
หมายเหตุสั้น ๆ : การติดตามExecutorServiceอินสแตนซ์นั้นดีกว่าดังนั้นคุณสามารถโทรติดต่อได้shutdown()เมื่อต้องการ
Daniel Szalay

1
หากคุณทำเช่นนี้คุณจะไม่ลงเอยด้วย ExecutorService ที่ไม่ได้ปิดซึ่งทำให้ JVM ของคุณปฏิเสธที่จะปิดเครื่องหรือไม่? ฉันอยากจะแนะนำให้เขียนวิธีการของคุณเองเพื่อรับ ExecutorService แบบเธรดเดียว จำกัด เวลา
djangofan

14

คุณสามารถใช้ไวยากรณ์ Java8 สำหรับ CompletableFuture ด้วยวิธีนี้คุณสามารถทำการคำนวณ async เพิ่มเติมตามผลลัพธ์จากการเรียกใช้ฟังก์ชัน async

ตัวอย่างเช่น:

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

สามารถดูรายละเอียดเพิ่มเติมได้ในบทความนี้


10

คุณสามารถใช้@Asyncคำอธิบายประกอบจากjcabi-sidedและ AspectJ:

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

เมื่อคุณโทรsave()เธรดใหม่จะเริ่มต้นและรันเนื้อหา เธรดหลักของคุณดำเนินต่อไปโดยไม่ต้องรอผลของsave().


1
โปรดทราบว่าวิธีนี้จะทำการเรียกทั้งหมดเพื่อบันทึกแบบอะซิงโครนัสซึ่งอาจเป็นหรือไม่ใช่สิ่งที่เราต้องการ ในกรณีที่ควรทำการเรียกเฉพาะบางวิธีไปยังวิธีการที่กำหนดแบบ async สามารถใช้วิธีการอื่น ๆ ที่แนะนำในหน้านี้ได้
bmorin

ฉันสามารถใช้ในแอปพลิเคชันบนเว็บได้หรือไม่ (เนื่องจากไม่แนะนำให้จัดการเธรดที่นั่น)
onlinenaman

8

คุณสามารถใช้ 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);
}

อ้างอิง: https://spring.io/guides/gs/async-method/


10
หากคุณใช้สปริงบูต
Giovanni Toraldo

6

Java ยังมีวิธีที่ดีในการเรียกใช้วิธี async ในjava.util.concurrentเรามีExecutorServiceที่ช่วยในการทำเช่นเดียวกัน เริ่มต้นวัตถุของคุณเช่นนี้ -

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

จากนั้นเรียกใช้ฟังก์ชันเช่น -

asyncExecutor.execute(() -> {

                        TimeUnit.SECONDS.sleep(3L);}

3

อาจไม่ใช่วิธีแก้ปัญหาที่แท้จริง แต่ตอนนี้ - ใน Java 8 - คุณสามารถทำให้โค้ดนี้ดูดีขึ้นอย่างน้อยก็เล็กน้อยโดยใช้นิพจน์แลมบ์ดา

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

และคุณสามารถทำได้ในบรรทัดเดียวโดยที่ยังอ่านได้สวย

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

เป็นเรื่องดีมากที่ใช้สิ่งนี้โดยใช้ Java 8
Mukti

3

คุณสามารถใช้AsyncFuncจากCactoos :

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

มันจะดำเนินการที่พื้นหลังและผลลัพธ์จะอยู่ในget()รูปแบบFutureไฟล์.


2

สิ่งนี้ไม่เกี่ยวข้องจริงๆ แต่ถ้าฉันจะเรียกเมธอดแบบอะซิงโครนัสเช่นการจับคู่ () ฉันจะใช้:

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

    });
}

จากนั้นเพื่อเรียกวิธีอะซิงโครนัสฉันจะใช้:

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

ฉันได้ทดสอบแล้วและได้ผล แค่คิดว่าอาจช่วยคนอื่นได้หากพวกเขาเพิ่งมาเพื่อ "วิธีอะซิงโครนัส"


1

นอกจากนี้ยังมีห้องสมุดที่ดีสำหรับ Async-Await ที่สร้างโดย EA: https://github.com/electronicarts/ea-async

จาก Readme ของพวกเขา:

ด้วย 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);
    }
}

ไม่มี 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);
            });
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.