1. 程式人生 > >#Java技術分享:從程式碼角度理性分析Java中的非同步與AIO

#Java技術分享:從程式碼角度理性分析Java中的非同步與AIO

非同步程式設計提供了一個非阻塞的,事件驅動的程式設計模型。 這種程式設計模型利用系統中多核執行任務來提供並行,因此提高了應用的吞吐率。Java非同步程式設計通常需要使用FutureFutureTaskCallable,其中Future代表未來的某個結果,一般是向執行緒池提交任務時返回。

如果有想學習java的程式設計師,可來我們的java學習扣qun:94311,1692免費送java的視訊教程噢!我整理了一份適合18年學習的java乾貨,送給每一位想學的小夥伴,並且每天晚上8點還會在群內直播講解Java知識,歡迎大家前來學習哦。

例:

public class PromiseTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        List<Future<String>> futureList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            int finalI = i;
            Future<String> future = executor.submit(() -> {
                System.out.println("task["+ finalI +"] started!");
                Thread.sleep(1000*(3-finalI));// cost some time
                System.out.println("task["+ finalI +"]finished!");
                return "result["+ finalI +"]";
            });
            futureList.add(future);
        }

        for (Future<String> future : futureList) {
            System.out.println(future.get());
        }
        System.out.println("Main thread finished.");
        executor.shutdown();
    }

}

執行結果:

task[0] started!
task[2] started!
task[1] started!
task[2]finished!
task[1]finished!
task[0]finished!
result[0]
result[1]
result[2]
Main thread finished.

有兩個問題值得注意:

  • 向執行緒池提交的三個任務同時開始執行,但是在使用get取結果的時候發現必須等耗時最長的任務結束之後才可以得到執行結果。
  • get方法阻塞了主執行緒,在取非同步任務執行結果期間主執行緒不可以做其他事情。

這和真正意義上的非同步程式設計模型是違背的,非同步程式設計不存線上程阻塞的情況,因此僅使用Future

來完成非同步程式設計是不科學的。

CompletionService

CompletionService提供了一種將生產新的非同步任務與使用已完成任務的結果分離開來的服務。生產者 submit 執行的任務,使用者 take 已完成的任務,並按照完成這些任務的順序處理它們的結果,使用者總是可以取到最先完成任務的結果。

public class PromiseTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        CompletionService<String> service = new ExecutorCompletionService<String>(executor);
        for (int i = 0; i < 3; i++) {
            int finalI = i;
            service.submit(() -> {
                System.out.println("task["+ finalI +"] started!");
                Thread.sleep(1000*(3-finalI));// cost some time
                System.out.println("task["+ finalI +"]finished!");
                return "result["+ finalI +"]";
            });
        }
        for (int i = 0; i < 3; i++) {
            System.out.println(service.take().get());
        }
        System.out.println("Main thread finished.");
        executor.shutdown();
    }

}

執行結果:

task[0] started!
task[2] started!
task[1] started!
task[2]finished!
result[2]
task[1]finished!
result[1]
task[0]finished!
result[0]
Main thread finished.

可見,最耗時的任務在執行時並不影響其他主執行緒取到已完成任務的結果。但主執行緒仍然被get方法阻塞。

CompletableFuture

在JDK8釋出之前,許多追求效能的框架都自己造了一套非同步的實現,例如guava的ListenableFuture和netty的FutureListener,一直到Java8,才有了真正的官方非同步實現, CompletableFuture提供了非同步操作的支援,使用方法與Js中的Promise類似。
關於CompletableFuture的詳細說明,請參考這篇:Java 8:CompletableFuture終極指南
使用這個API改寫一下程式碼:

public class PromiseTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 3; i++) {
            int finalI = i;
            CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
                System.out.println("task["+finalI+"] started!");
                try {
                    // time cost
                    Thread.sleep(1000*(3-finalI));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("task["+finalI+"] finished");
                return "result["+finalI+"]";
            }, executor);
            future.thenAccept(System.out::println);
        }

        System.out.println("Main thread finished.");
        executor.shutdown();
    }

}

結果與預期完全一致,各非同步任務獨自執行,互相不阻塞。

task[0] started!
Main thread finished.
task[1] started!
task[2] started!
task[2] finished
result[2]
task[1] finished
result[1]
task[0] finished
result[0]

非同步IO

從程式設計模型角度來講,IO操作方式有BIO/NIO/AIO,BIO就是傳統的阻塞IO模型,關於NIO的介紹,也請參考這篇: Java NIO基礎
與NIO區別的是,API提供的讀寫方法均是非同步的,即對流或通道內容進行讀寫時並不會阻塞呼叫的主執行緒,JDK7在java.nio.channels包新增了以下4個API來支援非同步IO:

  • AsynchronousSocketChannel
  • AsynchronousServerSocketChannel
  • AsynchronousFileChannel
  • AsynchronousDatagramChannel

AIO並沒有加速IO處理速度,只是利用回撥和通知機制改變了業務處理時機,使得具體邏輯可以不關注IO結果,只需在合理的時機添加回調即可。
例如:

public class AsyncServer {

    public static void main(String[] args) {
        try {
            AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8000));
            server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
                final ByteBuffer buffer = ByteBuffer.allocate(1024);

                @Override
                public void completed(AsynchronousSocketChannel result, Object attachment) {
                    Future<Integer> writeResult = null;
                    try {
                        buffer.clear();
                        result.read(buffer).get(100, TimeUnit.SECONDS);
                        buffer.flip();
                        writeResult = result.write(buffer);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            server.accept(null, this);
                            writeResult.get();
                            result.close();
                        } catch (Exception e) {
                            System.out.println(e.toString());
                        }
                    }
                }

                @Override
                public void failed(Throwable exc, Object attachment) {
                        
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

通過CompletionHandler來新增一些回撥,這樣可以很方便地進行非同步IO操作。