阅读 103

终于搞懂 BIO,NIO,AIO 了

前言

I/O,通常指数据在内部存储器(内存)和外部存储器(磁盘)之间的输入和输出。

Java 中提供了一些 API,可以供开发者来读写外部数据或文件,称这些 API 为 Java IO, 随着 Java的发展,目前有三种IO:BIONIOAIO,下面对这三种 IO 进行介绍。

在讲述BIONIOAIO前,先来弄清楚四个概念:同步异步,阻塞非阻塞。

同步/异步

同步/异步关注的是消息通信机制。

  • 同步(Synchronous) 是指发起一个调用后,调用方必须等待此调用返回结果后才能继续执行。
  • 异步(Asynchronous)是指发起一个调用后,调用方可继续执行后续操作,被调用者执行结束主动给调用方返回结果。

比如,同步就是主线程开始执行,调用其中一个sum()方法,主线程要等待sum()方法执行结束并返回结果再执行后续程序代码;而异步是主线程执行调用 sum()方法,有另一个线程去执行 sum() 方法内部逻辑,而主线程在调用完 sum() 后可执行后续程序代码,无需等到sum()结果返回才执行。

阻塞/非阻塞

阻塞/非阻塞关注的是程序在等待调用结果时的状态,区别在于第一步发起 IO 请求后是否会被阻塞。

  • 阻塞是指调用结果返回前,当前线程会被挂起,调用线程只有在得到结果之后才返回。
  • 非阻塞是指在调用结果返回前,不影响当前线程执行其他操作,也就是不会阻塞当前线程。

同步/异步经常会和阻塞/非阻塞混为一谈。

同步/异步指的是消息的通信机制,同步是指当前线程要拿到执行结果后再返回,异步是指当前线程无需等待执行结果,可以执行当前线程的后续操作,同步与异步的区别是如何获取执行结果的事情。阻塞/非阻塞指的是程序在调用后线程的状态,是基于同一个线程来说的,阻塞是指调用后线程被置为阻塞(Blocked) 的状态,非阻塞是指调用后线程仍然处于 运行(Running)的状态,阻塞与非阻塞的区别是当前线程是否处于阻塞的状态。所以,二者指的不是一个维度的概念。

BIO、NIO、AIO

我们用非常经典的烧水例子来说明这三个概念的区别。

BIO(Block IO)是一种同步阻塞的通信模式,是一个比较传统的通信方式,模式简单、使用方便,但并发处理能力低,通信耗时,依赖网速。

NIO(Non-Bock) IO 是一种同步非阻塞的通信模式,针对网络传输效能优化的新功能。

AIO(Asynchronous IO)是一种异步非阻塞的通信模式。

同步阻塞的工作模式是先来到厨房,开始烧水,并在水壶旁等待直到水开了为止。

同步非阻塞的工作模式是指来到厨房,开始烧水,这次不在水壶旁等着,回到客厅看电视,每隔几分钟去检查水是否烧开了。

异步非阻塞的工作模式是指来到厨房,开始烧水,回到客厅看电视,直到听到水烧开后水壶的提示音。

同步vs异步:指的是被调用方以何种方式返回调用结果?(我们如何知道水壶中的水烧开了,是主动发现还是水壶被动提醒)

阻塞vs非阻塞:指的是调用方在调用后的状态是否是阻塞?(我们把水壶烧水开关打开后,我们是否在水壶旁等待水烧开)

适用场景

BIO适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

NIO适用于连接数目比较多且连接比较短的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

AIO方式适用于连接数目多且连接比较长的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

实战

下面将从代码的角度出发,看看这三种架构模式的区别。

BIO

在 JDK1.4以前,用 Java 编写网络请求,都是建立一个 ServerSocket,客户端建立 socket时会询问是否有线程可以处理,如果没有,要么等待,要么被拒绝。

写文件

public class WriteFileTest {
    public static void main(String[] args) throws IOException {
        User user = new User();
        user.setAge(10);
        user.setName("feiyangyang");

        ObjectOutputStream oos = null;

        try {
            oos = new ObjectOutputStream(new FileOutputStream("bioFile"));
            oos.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            oos.close();
        }
    }
}
复制代码

读文件

public class ReadFileTest {
    public static void main(String[] args) throws IOException {
        File bioFile = new File("bioFile");
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(bioFile));
            User user = (User) ois.readObject();
            System.out.println(user);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            ois.close();
        }
    }
}
复制代码

NIO

在Java中,在JDK1.4以后提供了一套API专门操作非阻塞I/O,我们可以在 java.nio 包及其子包中找到相关类和接口。

这套 API 由三个主要部分组成:

  • 缓冲区 (Buffers)
  • 通道 (Channels)
  • 非阻塞 I/O 的核心类组成

写文件

public class WriteFileTest {
    public static void main(String[] args) {
        String fileName = "nioFile";
        try (FileOutputStream fos = new FileOutputStream(new File(fileName))) {
            FileChannel channel = fos.getChannel();
            ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode("你好你好..");
            int length = 0;
            while ((length = channel.write(byteBuffer)) != 0) {
                System.out.println("写入长度:" + length);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
复制代码

读文件

public class ReadFileTest {
    public static void main(String[] args) throws IOException {
        String fileName = "C:\IODemo\nioFile";
        try (FileInputStream fis = new FileInputStream(new File(fileName)); FileChannel channel = fis.getChannel()) {
            int capacity = 100;
            ByteBuffer byteBuffer = ByteBuffer.allocate(capacity);
            int length = -1;
            while ((length = channel.read(byteBuffer)) != -1) {
                byteBuffer.clear();
                byte[] array = byteBuffer.array();
                System.out.write(array, 0, length);
                System.out.println();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
复制代码

AIO

使用 java.nio 包中的 AsynchronousFileChannel 类和 CompletionHandler 分别做 通道 和 处理器。

写文件

public class WriteFileTest {
    public static void main(String[] args) throws IOException {
        AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
                Paths.get("aioFile.txt"), StandardOpenOption.READ,
                StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        CompletionHandler<Integer, Object> handler = new CompletionHandler<Integer, Object>() {

            @Override
            public void completed(Integer result, Object attachment) {
                System.out.println("Attachment: " + attachment + " " + result
                        + " bytes written");
                System.out.println("CompletionHandler Thread ID: "
                        + Thread.currentThread().getId());
            }

            @Override
            public void failed(Throwable e, Object attachment) {
                System.err.println("Attachment: " + attachment + " failed with:");
                e.printStackTrace();
            }
        };

        System.out.println("Main Thread ID: " + Thread.currentThread().getId());
        fileChannel.write(ByteBuffer.wrap("Sample".getBytes()), 0, "First Write",
                handler);
        fileChannel.write(ByteBuffer.wrap("Box".getBytes()), 0, "Second Write",
                handler);
    }
}
复制代码

读文件

public class ReadFileTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException, IOException {
        Path file = Paths.get("aioFile.txt");
        AsynchronousFileChannel channel = AsynchronousFileChannel.open(file);

        ByteBuffer buffer = ByteBuffer.allocate(10);
        Future<Integer> result = channel.read(buffer, 0);

        while (!result.isDone()) {
            ProfitCalculator.calculateTax();
        }
        Integer bytesRead = result.get();
        System.out.println("Bytes read [" + bytesRead + "]");
    }
}

class ProfitCalculator {
    public ProfitCalculator() {
    }

    public static void calculateTax() {
    }
}
复制代码

小结

本文主要介绍了 同步/异步阻塞/非阻塞BIONIOAIO 的基本概念和理解,如对其他内容感兴趣,可继续关注小编其他文章。

文章分类
后端
文章标签