Java伝統IO

5494 ワード

詳細
従来のIOには2つの形式があり、1つはIOをブロックすることであり、もう1つはIO+各要求をブロックしてスレッド/スレッドプールを作成することである.
ブロックIO
IOのブロック、非ブロックは主に1つのIO操作過程に現れ、もし一部の操作が遅い場合、例えば読み取り操作時にデータを準備する必要がある場合、現在のIOプロセスは操作が完了するのを待っているのか、それとも一時的に操作できないことを知った後、先に他のことをしますか?ずっと待っていて、何もしないで完成するまで、これは渋滞です.暇を見つけてほかのことをするのは,これは非ブロックである.
伝統的なIOではread()メソッドはブロックされ、データが来るまで(またはタイムアウト)返されます.同様に、ネットワークIO運用でサーバSocket.accept()メソッドを呼び出すと、クライアント接続があるまでブロックされます.
ネットワークIO操作を例にとると:
public static void main(String[] args) {
        Socket socket = null;
        try {
            ServerSocket ss = new ServerSocket(10000);
            while (true) {
                socket = ss.accept();//  
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String msg = null;
                while((msg = in.readLine()) != null){//readLine    
                    System.out.println(msg);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally{
          if(socket != null){
             socket.close();
           }
        }
}

この閉塞IOの欠陥は以下の通りである.
  • InputStreamが呼び出されている.read()/buffer.readLine()メソッドはブロックされており、StringBufferを使用して最適化できるにもかかわらず、データが到着したり、バッファがいっぱいになったり、タイムアウトしたりするまで待機し、Stringタイプのゴミが大量に発生します.
  • サーバソケットを呼び出しています.accept()メソッドの場合も、クライアント接続があるまでブロックされます.
  • サーバ側は単一スレッドであるため、最初の接続のクライアントがスレッドをブロックした後、2番目のクライアントは最初の切断を待ってから接続しなければならない.
  • すべてのクライアント接続は、要求されたサービス側でブロックされ、前の完了を待つ.短い接続を使用しても、OutputStreamへの書き込みやInputStreamからの読み込みでデータがブロックされる可能性があります.これは、大規模なアクセス量やシステムがパフォーマンスに要求されている場合には受け入れられません.

  • ブロックIO+各リクエスト作成スレッド
    クライアント要求ごとに個別のスレッドを作成することは、従来のブロックIOにとって最も一般的な解決策であり、例は以下の通りである.
    public static void main(String[] args) {
            try {
                ServerSocket ss = new ServerSocket(10000);
                while (true) {
                    final Socket socket = ss.accept();//  
                    new Thread(){
                        public void run(){
                            try{
                                  BufferedReader in = new BufferedReader(new InputStreamReader(
                                                      socket.getInputStream()));
                                  String msg = null;
                                  while((msg = in.readLine()) != null){//readLine    
                                         System.out.println(msg);
                                  }
                            }catch(Exception e){
                                     e.printStackTrace();
                           } finally{
                                 if(socket != null){
                                      socket.close();
                                 }
                             }
                        }
                    }.start();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    }
    この方式のIO動作モードは、サービス側が単一スレッドであるという欠陥を解決するが、多くの欠陥がある.
  • クライアントが多い場合、大量の処理スレッドが作成されます.各スレッドはスタックスペースとCPU時間を消費し、サーバへの圧力をもたらします.サービス側のスレッド数とクライアントの同時アクセス数は1:1の比例関係を呈し、スレッド数が急速に膨張すると、システムの性能は急激に低下し、アクセス量が増大し続けるにつれて、システムは最終的に死んでしまう.
  • 大量のスレッドは、サービス側の競合リソースの一部にアクセスする必要がある場合は、同期操作が必要になります.各スレッドは外部の準備ができていない場合にブロックされ、ブロックされた結果、大量のプロセスコンテキストの切り替えをもたらします.ほとんどのプロセスコンテキストの切り替えは意味がない可能性があります.たとえば、1つのスレッドが1つのポートを傍受していると仮定すると、1日に数回しか要求されませんが、cpuはスレッドのコンテキスト切替の試みを続けなければなりません.ほとんどの切替はブロックに終わります.

  • ブロックIO+各リクエストはスレッドプールで処理される
    実装は簡単です.新しいスレッドの場所をスレッドプール管理に渡すだけです.
     
    public static void main(String[] args) {
            try {
                ExecutorService executorService = Executors.newFixedThreadPool(60);
                ServerSocket ss = new ServerSocket(10000);
                while (true) {
                    final Socket socket = ss.accept();//  
                    executorService.execute(new Runnable(){
                         public void run(){
                            try{
                                  BufferedReader in = new BufferedReader(new InputStreamReader(
                                                      socket.getInputStream()));
                                  String msg = null;
                                  while((msg = in.readLine()) != null){//readLine    
                                         System.out.println(msg);
                                  }
                            }catch(Exception e){
                                e.printStackTrace();
                           } finally{
                                 if(socket != null){
                                      socket.close();
                                 }
                             }
                        }
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    }

    この方式は、1つまたは複数のスレッドがNクライアントの要求を処理することを実現するが、下位層は同期ブロックI/Oを使用する.
     
    FixedThreadPoolを使用すると、スレッドの最大数を効果的に制御し、システムの限られたリソースの制御を保証します.しかし、スレッドの数を制限しているだけに、大量の同時要求が発生すると、最大数を超えるスレッドはスレッドプール内の空きスレッドが多重化されるまで待つしかありません.Socketの入力ストリームを読み込むと、次のようにブロックされます.
  • データ読み取り可能
  • 利用可能なデータおよび読み出し完了
  • 空ポインタまたはI/O異常発生
  • したがって、データの読み取りが遅い場合(例えば、データ量が大きい場合、ネットワークの伝送が遅い場合など)、大量に同時発生する場合、他のアクセスメッセージは、ずっと待つしかないのが最大の弊害である.