Jetty NIO模型

概述

jetty NIO是典型reactor模型,如下圖所示:


即:mainReactor負責監聽server socket,接受新連接,並將建立的socket分派給subReactor。subReactor負責多路分離已連接的socket,讀寫網絡數據,扔給worker線程池來處理。本文主要是講解jetty中mainReactor、subReactor、線程池的實現。

mainReactor

jetty中的server就相當於一個容器,一個jetty容器包含多個連接器和一個線程池,連接器實現了LifeCycle接口,隨容器啓動而啓動,下圖是連接器啓動後,監聽server socket,建立連接的過程:


可見,jetty利用了線程池來建立連接,每一個連接任務被當成一個job被放到了job隊列裏面,負責連接的線程會從隊列中取出任務來執行,將得到的ServerSocket交給subReactor,下面來看subReactor的實現。

subReactor

這裏需要提一下jetty nio很重要的一個類SelectorManager,它負責channel註冊,select,wakeup等操作。在SelectorManager中有SelectSet數組,可以把SelectSet理解爲SelectorManager的代理,因爲真正做事的是SelectSet,這裏面SelectSet設計爲一個數組,應該也是分而治之的思想,讓一個selector監聽更少的selectionkey。

SelectSet中有一個非常重要的成員changes,changes中存放了所有有變化的channel、endpoint、attachement。分別在以下情況觸發addChannel方法:當有新的通道加入時,當有新的事件到來時,當有數據到來時。

subReactor的執行流程如下圖:


在這裏導致addChange除了selectorManager.register之外,還有endpoint.updatekey()以及selectionkey數據有變化時等等。

ThreadPool

jetty的線程池相當簡單,其實mainReactor與subReactor共用同一個線程池,線程池的實現類是QueuedThreadPool,當然在jetty.xml中可以設置自己的線程池類。簡單看下線程池的run方法

private Runnable _runnable = new Runnable()
  {
      public void run()
      {
          boolean shrink=false;
          try
          {
              Runnable job=_jobs.poll();
              while (isRunning())
              {
                  // Job loop
                  while (job!=null && isRunning())
                  {
                      runJob(job);
                      job=_jobs.poll();
                  }

                  // Idle loop
                  try
                  {
                      _threadsIdle.incrementAndGet();

                      while (isRunning() && job==null)
                      {
                          if (_maxIdleTimeMs<=0)
                              job=_jobs.take();
                          else
                          {
                              // maybe we should shrink?
                              final int size=_threadsStarted.get();
                              if (size>_minThreads)
                              {
                                  long last=_lastShrink.get();
                                  long now=System.currentTimeMillis();
                                  if (last==0 || (now-last)>_maxIdleTimeMs)
                                  {
                                      shrink=_lastShrink.compareAndSet(last,now) &&
                                      _threadsStarted.compareAndSet(size,size-1);
                                      if (shrink)
                                          return;
                                  }
                              }
                              job=idleJobPoll();
                          }
                      }
                  }
                  finally
                  {
                      _threadsIdle.decrementAndGet();
                  }
              }
          }
          catch(InterruptedException e)
          {
         		...
          }
      }
  };
1、線程池有個最小線程數_minThreads=8,當線程池啓動時會創建_minThreads個線程,並啓動它們。第12行,線程從任務隊列中取出一個任務,並執行。這裏使用了while循環表示這裏會阻塞等待任務執行完,當任務隊列中沒有任務時,纔會退出while循環;

2、退出while循環後,這個線程就空閒了,在這裏需要有個回收策略,在等待_maxIdleTimeMs時間後,如果當前線程數大於_minThreads時,就會回收這個線程。

那麼線程數什麼時候會大於_minThreads?來看看dispatch()方法中的核心代碼

 // If we had no idle threads or the jobQ is greater than the idle threads
                if (idle==0 || jobQ>idle)
                {
                    int threads=_threadsStarted.get();
                    if (threads<_maxThreads)
                        startThread(threads);
                }
如果沒有空閒的線程或者空閒線程數太少,在保證線程數沒有超過_maxThreads時會新建線程。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章