Cách gọi không đồng bộ một phương thức trong Java


127

Gần đây, tôi đã xem xét các goroutines của Go và nghĩ rằng sẽ rất tuyệt nếu có thứ gì đó tương tự trong Java. Theo như tôi đã tìm kiếm, cách phổ biến để song song một cuộc gọi phương thức là thực hiện một số việc như:

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

Đó không phải là rất thanh lịch. Có cách nào tốt hơn để làm điều này không? Tôi cần một giải pháp như vậy trong một dự án vì vậy tôi quyết định triển khai lớp trình bao bọc của riêng mình xung quanh một cuộc gọi phương thức không đồng bộ.

Tôi đã xuất bản lớp trình bao bọc của mình trong J-Go . Nhưng tôi không biết đó có phải là một giải pháp tốt hay không. Cách sử dụng rất đơn giản:

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

hoặc ít dài dòng:

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

Bên trong, tôi đang sử dụng một lớp triển khai Runnable và thực hiện một số công việc Reflection để lấy đối tượng phương thức chính xác và gọi nó.

Tôi muốn một số ý kiến ​​về thư viện nhỏ của tôi và về chủ đề thực hiện các cuộc gọi phương thức không đồng bộ như thế này trong Java. Nó có an toàn không? Đã có một cách đơn giản hơn?


1
Bạn có thể hiển thị lại mã J-Go lib của mình không?
msangel

Tôi đang làm việc trong một dự án mà ở đó tôi đã đọc một cách thông thái về luồng. Khi một từ hoàn chỉnh được đọc, tôi đã thực hiện nhiều thao tác trên từ đó. Cuối cùng tôi đưa nó vào một bộ sưu tập. Khi tất cả dữ liệu từ luồng được đọc, tôi trả lại phản hồi. Tôi quyết định bắt đầu chuỗi cho mỗi lần tôi thực hiện thao tác trên một từ. Nhưng nó làm giảm hiệu suất tổng thể. Sau đó, tôi biết rằng bản thân các luồng là một hoạt động tốn kém. Tôi không chắc liệu việc bắt đầu một luồng để gọi một phương thức đồng thời có thể cung cấp thêm bất kỳ hiệu suất nào cho đến khi nó thực hiện bất kỳ hoạt động IO nặng nào hay không.
Amit Kumar Gupta,

2
Câu hỏi tuyệt vời!! Java 8 cung cấp CompletableFutures cho việc này. Câu trả lời khác đều dựa trên các phiên bản cũ của lẽ Java
Tricore

Câu trả lời:


155

Tôi vừa phát hiện ra rằng có một cách dễ dàng hơn để làm

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

(Ít nhất là trong Java 8), bạn có thể sử dụng biểu thức lambda để rút gọn nó thành:

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

Đơn giản như tạo một hàm trong JS!


Câu trả lời của bạn đã giúp ích cho vấn đề của tôi - stackoverflow.com/questions/27009448/… . Ít khó khăn để áp dụng situatioin của tôi, nhưng làm việc nó ra cuối cùng :)
Deckard

Làm thế nào để gọi với các tham số?
yatanadam 29/12/16

2
@yatanadam Điều này có thể trả lời câu hỏi của bạn. Chỉ cần đặt đoạn mã trên bên trong một phương thức và dán biến như bạn thường làm. Kiểm tra mã thử nghiệm này tôi đã thực hiện cho bạn.
user3004449

1
@eNnillaMS Chuỗi có phải dừng sau khi chạy không? Nó tự động dừng hay do bộ thu gom rác ?
user3004449

@ user3004449 Chủ đề tự động dừng, tuy nhiên, bạn cũng có thể buộc nó.
Shawn

59

Java 8 đã giới thiệu CompletableFuture có sẵn trong gói java.util.concurrent.CompletableFuture, có thể được sử dụng để thực hiện cuộc gọi không đồng bộ:

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

CompletableFutuređã được đề cập trong một câu trả lời khác, nhưng câu trả lời đó đã sử dụng toàn bộ supplyAsync(...)chuỗi. Đây là một trình bao bọc đơn giản phù hợp với câu hỏi một cách hoàn hảo.
ndm13

ForkJoinPool.commonPool().execute()có hơi ít overhead
Đánh dấu Jeronimus

31

Bạn cũng có thể muốn xem xét lớp học java.util.concurrent.FutureTask.

Nếu bạn đang sử dụng Java 5 trở lên, FutureTask là một triển khai chìa khóa trao tay của "Một phép tính không đồng bộ có thể hủy bỏ".

Thậm chí có nhiều hành vi lập lịch thực thi không đồng bộ phong phú hơn có sẵn trong java.util.concurrentgói (ví dụ ScheduledExecutorService:), nhưng FutureTaskcó thể có tất cả các chức năng bạn yêu cầu.

Tôi thậm chí còn đi xa hơn khi nói rằng không còn nên sử dụng mẫu mã đầu tiên mà bạn đã đưa ra làm ví dụ kể từ khi FutureTaskcó sẵn. (Giả sử bạn đang sử dụng Java 5 trở lên.)


Vấn đề là tôi chỉ muốn thực hiện một cuộc gọi phương thức. Bằng cách này, tôi sẽ phải thay đổi việc triển khai lớp mục tiêu. Điều tôi muốn là chính xác cuộc gọi mà không cần phải lo lắng về việc thực hiện Runnable hoặc Callable
Felipe Hummel

Tôi nghe bạn. Java không (chưa) có các hàm hạng nhất, vì vậy đây là trạng thái hiện đại.
shadit

cảm ơn vì các từ khóa 'tương lai' ... bây giờ tôi đang mở hướng dẫn về chúng ... rất hữu ích. : D
gumuruh

4
-1 theo như tôi có thể nói, FutureTask tự nó không đủ để chạy bất kỳ thứ gì không đồng bộ. Bạn vẫn cần tạo một Thread hoặc Executor để chạy nó, như trong ví dụ của Carlos.
MikeFHay

24

tôi không thích ý tưởng sử dụng Reflection cho việc đó.
Không chỉ nguy hiểm nếu bỏ sót nó trong một số lần tái cấu trúc, mà còn có thể bị từ chối bởi SecurityManager.

FutureTasklà một tùy chọn tốt như các tùy chọn khác từ gói java.util.concurrent.
Yêu thích của tôi cho các nhiệm vụ đơn giản:

    Executors.newSingleThreadExecutor().submit(task);

ít cắn ngắn hơn so với việc tạo ra một chủ đề (nhiệm vụ là một Callable hoặc một Runnable)


1
Vấn đề là tôi chỉ muốn thực hiện một cuộc gọi phương thức. Bằng cách này, tôi sẽ phải thay đổi việc triển khai lớp mục tiêu. Điều tôi muốn là chính xác cuộc gọi mà không cần phải lo lắng về việc thực hiện Runnable hoặc Callable
Felipe Hummel

sau đó giúp đỡ would'nt này nhiều :( nhưng thường tôi muốn sử dụng một Runnable hoặc Callable thay vì Reflection
user85421

4
Chỉ cần một lưu ý ngắn: Theo dõi ExecutorServicetrường hợp này sẽ tốt hơn , vì vậy bạn có thể gọi shutdown()khi cần thiết.
Daniel Szalay

1
Nếu bạn làm điều này, bạn có thể kết thúc với ExecutorService chưa đóng, khiến JVM của bạn từ chối tắt không? Tôi khuyên bạn nên viết phương thức của riêng bạn để có được ExecutorService một luồng, có giới hạn thời gian.
djangofan

14

Bạn có thể sử dụng cú pháp Java8 cho CompletableFuture, bằng cách này, bạn có thể thực hiện các tính toán không đồng bộ bổ sung dựa trên kết quả từ việc gọi một hàm không đồng bộ.

ví dụ:

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

Thông tin chi tiết có thể được tìm thấy trong bài viết này


10

Bạn có thể sử dụng @Asyncchú thích từ jcabi-khía cạnh và AspectJ:

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

Khi bạn gọi save(), một luồng mới bắt đầu và thực thi phần thân của nó. Chuỗi chính của bạn tiếp tục mà không cần đợi kết quả của save().


1
Lưu ý rằng cách tiếp cận này sẽ thực hiện tất cả các cuộc gọi để lưu không đồng bộ, điều này có thể có hoặc có thể không như những gì chúng ta muốn. Trong trường hợp chỉ một số lệnh gọi đến một phương thức nhất định được thực hiện không đồng bộ, có thể sử dụng các phương pháp khác được đề xuất trên trang này.
bmorin

Tôi có thể sử dụng nó trong một ứng dụng web không (vì quản lý chuỗi không được khuyến khích ở đó)?
onlinenaman

8

Bạn có thể sử dụng Future-AsyncResult cho việc này.

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

Tham khảo: https://spring.io/guides/gs/async-method/


10
nếu bạn đang sử dụng spring-boot.
Giovanni Toraldo

6

Java cũng cung cấp một cách hay để gọi các phương thức không đồng bộ. trong java.util.concurrent, chúng tôi có ExecutorService giúp làm điều tương tự. Khởi tạo đối tượng của bạn như thế này -

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

và sau đó gọi hàm như-

asyncExecutor.execute(() -> {

                        TimeUnit.SECONDS.sleep(3L);}

3

Nó có lẽ không phải là một giải pháp thực sự, nhưng bây giờ - trong Java 8 - Bạn có thể làm cho mã này trông đẹp hơn một chút bằng cách sử dụng biểu thức lambda.

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

Và bạn thậm chí có thể làm điều này trong một dòng, vẫn có thể đọc được.

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

Thực sự rất tuyệt khi sử dụng điều này bằng Java 8.
Mukti

3

Bạn có thể sử dụng AsyncFunctừ Cactoos :

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

Nó sẽ được thực thi ở chế độ nền và kết quả sẽ có sẵn get()dưới dạng a Future.


2

Điều này không thực sự liên quan nhưng nếu tôi gọi một phương thức không đồng bộ, ví dụ: match (), tôi sẽ sử dụng:

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

    });
}

Sau đó, để gọi phương thức không đồng bộ, tôi sẽ sử dụng:

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

Tôi đã thử nghiệm điều này và nó hoạt động. Chỉ nghĩ rằng nó có thể giúp ích cho những người khác nếu họ đến với "phương pháp không đồng bộ".


1

Ngoài ra còn có thư viện tuyệt vời cho Async-Await được tạo bởi EA: https://github.com/electronicarts/ea-async

Từ Readme của họ:

Với 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);
    }
}

Không có 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);
            });
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.