ホームページ  >  記事  >  Java  >  JAVAのI/Oモデルの詳細な説明(例付き)

JAVAのI/Oモデルの詳細な説明(例付き)

王林
王林転載
2019-08-30 13:49:522051ブラウズ

おそらく多くの友人は、NIO を学習するのが少し難しいと感じるでしょうし、NIO の概念の多くはそれほど明確ではありません。 Java NIO プログラミングに入る前に、今日は I/O モデルという基本的な知識について説明しましょう。次の記事では、同期と非同期の概念から始まり、ブロッキングと非ブロッキングの違いを説明し、次にブロッキング IO と非ブロッキング IO の違いを紹介し、次に同期 IO と非同期 IO の違いを紹介します。 5 つの IO モデル、最後に高性能 IO 設計に関連する 2 つの設計モデル (Reactor と Proactor) を紹介します。

以下は、この記事の目次の概要です。

1. とは同期?非同期とは何ですか?

2. ブロッキングとは何ですか?ノンブロッキングとは何ですか?

3. ブロッキング IO とは何ですか?ノンブロッキングIOとは何ですか?

4. 同期 IO とは何ですか?非同期IOとは何ですか?

5. 5 つの IO モデル

6. 2 つの高性能 IO 設計パターン

1. 同期とは何ですか?非同期とは何ですか?

同期と非同期の概念は古くから存在しており、インターネット上には同期と非同期について多くの意見があります。以下は私の個人的な理解です:

同期とは、複数のタスクまたはイベントが発生する場合、これらのタスクまたはイベントを 1 つずつ実行する必要があり、1 つのイベントまたはタスクの実行によりプロセス全体が実行されます。これらのイベントは同時に実行できません。

非同期とは、複数のタスクまたはイベントが発生した場合、これらのイベントを同時に実行でき、1 つのイベントまたはタスクの実行によってプロセス全体が待機することはありません。一時的に。

これは同期および非同期です。簡単な例を挙げると、2 つのサブタスク A と B を含むタスクがあるとします。同期の場合、A の実行中、B は A が完了するまで待機してから B を実行できます。非同期の場合、A と B は実行できます。また、B は A の実行が完了するまで待つ必要がないため、A の実行によってタスク全体が一時的に待機することはありません。

それでも理解できない場合は、まず次の 2 つのコードを読んでください:

void fun1() {
       
  }
   
  void fun2() {
       
  }
   
  void function(){
      fun1();
      fun2()
      .....
      .....
  }

このコードは典型的な同期です。メソッド関数では、fun1 によって後続の fun2 が引き起こされます。 fun2 を実行するには、fun1 の実行が完了するまで待つ必要があります。

次に、次のコードを見てください:

void fun1() {
     
}
 
void fun2() {
     
}
 
void function(){
    new Thread(){
        public void run() {
            fun1();
        }
    }.start();
     
    new Thread(){
        public void run() {
            fun2();
        }
    }.start();
 
    .....
    .....
}

このコードは典型的な非同期です。fun1 の実行は fun2 の実行に影響を与えず、fun1 と fun2 の実行によって問題が発生することはありません。以降の実行処理は一時的に保留されます。

実際、同期と非同期は非常に幅広い概念であり、その焦点は、複数のタスクやイベントが発生したときに、イベントの発生または実行によってプロセス全体が一時的に待機するかどうかにあります。 。 Java の synchronized キーワードを使用すると、同期と非同期を類似させることができると思います。複数のスレッドが同時に変数にアクセスする場合、各スレッドの変数へのアクセスはイベントです。同期の場合、これらのスレッドは 1 つずつ変数にアクセスする必要があります。1 つのスレッドが変数にアクセスしている間、他のスレッドは待機する必要があります。; 非同期の場合、複数のスレッドが変数に 1 つずつアクセスする必要はなく、同時にアクセスできます。

したがって、同期と非同期はさまざまな方法で表現できると個人的に感じていますが、覚えておくべき重要な点は、複数のタスクやイベントが発生した場合、イベントの発生または実行によってプロセス全体が影響を受けるということです。一時的に待ちます。一般に、非同期はマルチスレッドによって実現できますが、マルチスレッドと非同期を同一視しないように注意してください。非同期は単なるマクロ パターンであり、マルチスレッドを使用して非同期を実現するのは単なる手段であり、非同期の実装も実現できます。マルチプロセッシングを通じて。

2. ブロックとは何ですか?ノンブロッキングとは何ですか?

同期と非同期の違いについては前に紹介しましたが、このセクションではブロッキングとノンブロッキングの違いについて見ていきます。

ブロッキングとは: イベントやタスクの実行時にリクエストオペレーションを発行しますが、リクエストオペレーションに必要な条件が満たされていないため、常にそこで待機します。条件が満たされるまで;

ノンブロッキング手段: イベントやタスクの実行時にリクエストオペレーションを発行します。リクエストオペレーションに必要な条件が満たされない場合、条件が満たされていないことを通知するフラグ メッセージを即座に返し、そこで永久に待機することはありません。

これは、ブロッキングと非ブロッキングの違いです。つまり、ブロッキングと非ブロッキングの主な違いは、

操作のリクエストが行われたとき、条件が満たされない場合、永久に待機するかフラグ メッセージを返すかどうかです。

簡単な例を挙げます:

ファイルの内容を読み取りたい場合、現時点でファイル内に読み取り可能な内容がなければ、同期のために常にそこで待機します。ファイル内に読み取り可能なコンテンツが存在するまで、非ブロッキングの場合は、ファイル内に読み取り可能なコンテンツがないことを通知するフラグ メッセージが直接返されます。

インターネット上の友人の中には、同期と非同期をそれぞれブロッキングとノンブロッキングと同一視する人もいますが、実際、これらはまったく異なる概念です。これら 2 つの概念セットの違いを理解することは、その後の IO モデルを理解する上で非常に重要であることに注意してください。

同期と非同期の焦点は、1 つのタスクの実行により、複数のタスクの実行中にプロセス全体が一時的に待機するかどうかにあります。

ブロッキングと非ブロッキングの焦点は、リクエスト発行時 動作中に、動作条件が満たされない場合に、条件が満たされていないことを通知するフラグメッセージを返すかどうか。

ブロッキングとノンブロッキングは、スレッドのブロッキングと同様に理解できます。スレッドがリクエスト操作を実行するとき、条件が満たされない場合、スレッドはブロックされます。つまり、条件が満たされるのを待ちます。会った。

3. ブロッキング I/O とは何ですか? ノンブロッキング I/O とは何ですか?

ブロッキング I/O と非ブロッキング IO ブロッキング IO の前に、まず次の特定の IO 操作プロセスがどのように実行されるかを確認します。

一般的に、IO 操作には、ハードディスクへの読み取りと書き込み、ソケットへの読み取りと書き込み、周辺機器への読み取りと書き込みが含まれます。

ユーザー スレッドが IO リクエスト操作を開始すると (この記事では読み取りリクエスト操作を例にします)、カーネルは読み取られるデータの準備ができているかどうかを確認します。準備ができている場合は、データの準備ができるまで待機し続けます。ノンブロッキング IO の場合、データの準備ができていない場合は、現在読み取られるデータの準備ができていないことをユーザー スレッドに通知するフラグ メッセージが返されます。データの準備ができたら、データはユーザー スレッドにコピーされ、完全な IO 読み取りリクエスト操作が完了します。つまり、完全な IO 読み取りリクエスト操作には 2 つの段階が含まれます:

1) ビューdata Is it ready?

2) データをコピーします (カーネルはデータをユーザー スレッドにコピーします)。

次に、ブロッキング (ブロッキング IO) とノンブロッキング (ノンブロッキング IO) の違いは、最初の段階で、データの準備ができていない場合に、チェックのプロセスでずっと待つかどうかです。データの準備ができているかどうか、または直接 A フラグ メッセージを返すかどうか。

Java の従来の IO は、ソケットを介したデータの読み取りなどの IO をブロックしています。read() メソッドを呼び出した後、データの準備ができていない場合、現在のスレッドは、データが準備できるまで、読み取りメソッドの呼び出しでブロックされます。ノンブロッキング IO の場合、データの準備ができていないとき、read() メソッドは、常にそこで待機するのではなく、現在のスレッドにデータの準備ができていないことを通知するフラグ メッセージを返す必要があります。

4. 同期 I/O とは何ですか? 非同期 I/O とは何ですか?

まず、同期 IO と非同期 IO の定義を見てみましょう。書籍「Unix ネットワーク プログラミング」における同期 IO と非同期 IO の定義は次のとおりです。

A 同期 I/O 操作では、その I/O 操作が完了するまで要求側のプロセスがブロックされます。 非同期 I/O 操作では、要求側のプロセスがブロックされません。<br>

これは文字通りの意味からわかります: 同期 IO は、スレッドが IO 操作を要求した場合、IO 操作が完了する前にスレッドがブロックされることを意味します;

非同期 IO は、スレッドが IO 操作を要求した場合にブロックされることを意味します。スレッドが IO 操作を要求すると、スレッドはブロックされます。IO 操作では、要求スレッドはブロックされません。

実際、同期 IO モデルと非同期 IO モデルは、ユーザー スレッドとカーネル間の対話を目的としています。

同期 IO の場合: ユーザーが IO リクエスト操作を発行した後、データの準備ができていません。ユーザー スレッドまたはカーネルを通じてデータの準備ができているかどうかを継続的にポーリングする必要があります。データの準備ができたら、データはカーネルからユーザー スレッドにコピーされます。

非同期 IO : IO 要求操作のみがユーザー スレッドによって発行されます。IO 操作の 2 段階はカーネルによって自動的に完了し、IO 操作が完了したことをユーザー スレッドに通知する通知が送信されます。つまり、非同期 IO では、ユーザー スレッドがブロックされることはありません。

これは、同期 IO と非同期 IO の主な違いです。

同期 IO と非同期 IO の主な違いは、データ コピー フェーズがユーザー スレッドによって完了されるかカーネルによって完了されるかに反映されます。したがって、非同期 IO にはオペレーティング システムによる基礎的なサポートが必要です

同期 IO と非同期 IO は、ブロッキング IO やノンブロッキング IO とは異なる 2 つの概念であることに注意してください。

ブロッキング IO とノンブロッキング IO は、ユーザーが IO 操作を要求したときに、データの準備ができていない場合でも、データの準備が完了するまでユーザー スレッドがフラグ メッセージを受け取るという事実に反映されています。準備ができている。つまり、ブロッキング IO とノンブロッキング IO は、IO 操作の最初の段階、つまりデータの準備ができているかどうかを確認する際の処理方法に反映されます。

5. 5 つの I/O モデル

書籍「Unix Network Programming」には、それぞれ 5 つの IO モデルが記載されています。ブロッキング IO、ノンブロッキング IO、多重化 IO、シグナルドリブン IO、および非同期 IO。

それでは、これら 5 つの IO モデルの類似点と相違点をそれぞれ紹介しましょう。

1. ブロッキング IO モデル

最も伝統的な IO モデル、つまりブロッキングは、データの読み取りと書き込みのプロセス中に発生します。

  当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除block状态。

  典型的阻塞IO模型的例子为:

data = socket.read();

如果数据没有就绪,就会一直阻塞在read方法。

2.非阻塞IO模型

  当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。

  所以事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。

  典型的非阻塞IO模型一般如下:

while(true){
    data = socket.read();
    if(data!= error){
        处理数据
        break;
    }
}

但是对于非阻塞IO就有一个非常严重的问题,在while循环中需要不断地去询问内核数据是否就绪,这样会导致CPU占用率非常高,因此一般情况下很少使用while循环这种方式来读取数据。

3.多路复用IO模型

  多路复用IO模型是目前使用得比较多的模型。Java NIO实际上就是多路复用IO。

  在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。

  在Java NIO中,是通过selector.select()去查询每个通道是否有到达事件,如果没有事件,则一直阻塞在那里,因此这种方式会导致用户线程的阻塞。

  也许有朋友会说,我可以采用 多线程+ 阻塞IO 达到类似的效果,但是由于在多线程 + 阻塞IO 中,每个socket对应一个线程,这样会造成很大的资源占用,并且尤其是对于长连接来说,线程的资源一直不会释放,如果后面陆续有很多连接的话,就会造成性能上的瓶颈。

  而多路复用IO模式,通过一个线程就可以管理多个socket,只有当socket真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO比较适合连接数比较多的情况。

  另外多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO中,不断地询问socket状态时通过用户线程去进行的,而在多路复用IO中,轮询每个socket状态是内核在进行的,这个效率要比用户线程要高的多。

  不过要注意的是,多路复用IO模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件逐一进行响应。因此对于多路复用IO模型来说,一旦事件响应体很大,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询。

4.信号驱动IO模型

  在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。

5.异步IO模型

  异步IO模型才是最理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。

つまり、非同期 IO モデルでは、IO 操作のどちらのフェーズもユーザー スレッドをブロックしません。両方のフェーズはカーネルによって自動的に完了し、操作が完了したことをユーザー スレッドに通知する信号が送信されます。完成しました。特定の読み取りおよび書き込みのためにユーザー スレッドで IO 関数を再度呼び出す必要はありません。これはシグナル駆動モデルとは異なります。シグナル駆動モデルでは、ユーザー スレッドがシグナルを受信すると、データの準備ができたことを示し、ユーザー スレッドは実際の読み取りと読み取りを実行するために IO 関数を呼び出す必要があります。書き込み操作。非同期 IO モデルでは、シグナルの受信は IO 操作が完了したことを示し、実際の読み取りおよび書き込み操作のためにユーザー スレッドで IO 関数を呼び出す必要はありません。

非同期 IO には、オペレーティング システムによる基礎的なサポートが必要であることに注意してください。Java 7 では、非同期 IO が提供されます。

最初の 4 つの IO モデルは実際には同期 IO であり、最後の 1 つだけが真の非同期 IO です。これは、それが多重化された IO であっても信号駆動型モデルであっても、2 番目の IO 操作は各段階でユーザー スレッドがブロックされます。つまり、カーネルによるデータ コピーのプロセスによりユーザー スレッドがブロックされます。

6. 2 つの高性能 I/O 設計パターン

従来のネットワーク サービス設計パターンには、さらに 2 つの古典的なパターンがあります:

1 つはマルチスレッド、 1 つはスレッド プールです。

マルチスレッド モードの場合、つまりクライアントが到着すると、次の図に示すように、サーバーはクライアントの読み取りおよび書き込みイベントを処理する新しいスレッドを作成します。

JAVAのI/Oモデルの詳細な説明(例付き)

このモードはシンプルで扱いやすいですが、サーバーはスレッドを使用して各クライアント接続を処理するため、多くのリソースを消費します。したがって、接続数が上限に達し、別のユーザーが接続を要求すると、リソースのボトルネックが直接発生し、ひどい場合にはサーバーのクラッシュにつながる可能性があります。

したがって、1 つのクライアント モードに対応する 1 つのスレッドによって引き起こされる問題を解決するために、固定サイズのスレッド プールを作成し、クライアントが来たときにスレッド プールを作成するスレッド プール方式が提案されます。スレッド プールはアイドル状態のスレッドを処理に使用し、クライアントが読み取りおよび書き込み操作を完了すると、スレッドの占有を引き渡します。したがって、これにより、クライアントごとにスレッドを作成することによるリソースの無駄が回避され、スレッドを再利用できるようになります。

ただし、スレッド プールには欠点もあります。ほとんどの接続が長い接続である場合、スレッド プール内のすべてのスレッドが一定期間占有される可能性があります。その後、別のユーザーが接続を要求すると、利用可能なアイドル スレッドが処理に使用されると、クライアント接続が失敗し、ユーザー エクスペリエンスに影響します。したがって、スレッド プールは、多数の短い接続アプリケーションにより適しています。

したがって、

ReactorProactor という 2 つの高性能 IO 設計パターンが登場しました。

Reactor モードでは、対象となるイベントが最初に各クライアントに登録され、次にスレッドが各クライアントをポーリングしてイベントが発生するかどうかを確認します。イベントが発生すると、各クライアントは順番に処理されます。 、すべてのイベントが処理されると、次の図に示すように、ポーリングを続行するために転送されます。

JAVAのI/Oモデルの詳細な説明(例付き)

ここからわかるように、上記の 5 つの IO モデルのうち、多重化された IO は Reactor モードを使用します。なお、上図では各イベントが順次処理されることを示していますが、もちろんイベントの処理速度を向上させるために、マルチスレッドやスレッドプールを介してイベントを処理することも可能です。

プロアクター モードでは、イベントが検出されると、新しい非同期操作が開始され、処理のためにカーネル スレッドに渡されます。カーネル スレッドが IO 操作を完了すると、通知が送信されます。非同期 IO モデルは Proactor モードを使用していることがわかります。

上記の内容に誤りがある場合はご容赦ください。また、批判や修正を歓迎します。


関連コンテンツをさらに知りたい場合は、PHP 中国語 Web サイトにアクセスしてください:

JAVA ビデオ チュートリアル

以上がJAVAのI/Oモデルの詳細な説明(例付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcnblogs.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。