1. 程式人生 > >電商之梳理servlet知識---javaweb基礎

電商之梳理servlet知識---javaweb基礎

servelt
是用java程式編寫的伺服器端程式,其主要功能是互動地瀏覽和修改資料(查詢和修改)。狹義說的是介面,廣義是指實現該介面的servlet類。
響應任何型別的請求,但是絕大數情況被用來 擴充套件基於HTTP協議的web伺服器的效能。
起源:
首先說起java applet, 一種當做單獨檔案跟網頁一起傳送的小程式,通常在客戶端執行,結果得到一些為使用者進行運算,或者根據使用者互動行為 定點陣圖形等服務。
然而,提供使用者輸入訪問資料庫的程式,通常使用CGI(Common GateWay Interface)來完成的,並且執行速度較慢。
此時便產生了 java servlet。
實現過程


要了解他的過程,那麼首先了解他的功能。
主要功能是:互動地瀏覽和修改資料,生成動態的web內容供使用者使用。
1. 客戶端傳送請求至伺服器端
2. 伺服器端將請求資訊傳送至servlet類
3. servlet生成響應內容傳至伺服器,響應內容動態生成,取決於客戶端的請求。
4. 伺服器端將請求資訊傳送至servlet。
擴充套件:
1. servlet 匯入了java servlet api相關的包
2. 物件位元組碼,可以被網路動態載入。
3. servlet執行與server中不需圖形使用者介面,applet執行在client環境中是需要圖形使用者介面。由此得來FacelessObject的別名。
4. 命名是sun公司的拿手好戲:當然是從applet裡來的,一個script加上一個小應用程式=scriptlet 表示小指令碼程式。
一個service加上一個小應用程式=servlet表示小服務程式。
生命週期

1. client—request—指定的servlet
2. load —-servlet—-記憶體中
3. 例項化—init()—初始化servlet
4. 呼叫類中的service() —-根據請求方式不同:選擇:doGet()或者doPost() 還有其他方法不常用(doHead()、doPut()、doTrace()、doDelete()、doOptions())
5. 最後呼叫destroy()進行銷燬。

分解其一生:
1. 載入過程
2. 例項化過程
3. 初始化過程
4. 銷燬過程

載入和例項化 Servlet。這項操作一般是動態執行的。然而,Server 通常會提供一個管理的選項,用於在 Server 啟動時強制裝載和初始化特定的 Servlet。
Server 建立一個 Servlet的例項
第一個客戶端的請求到達 Server
Server 呼叫 Servlet 的 init() 方法(可配置為 Server 建立 Servlet 例項時呼叫,在 web.xml 中 標籤下配置 標籤,配置的值為整型,值越小 Servlet 的啟動優先順序越高)
一個客戶端的請求到達 Server
Server 建立一個請求物件,處理客戶端請求
Server 建立一個響應物件,響應客戶端請求
Server 啟用 Servlet 的 service() 方法,傳遞請求和響應物件作為引數
service() 方法獲得關於請求物件的資訊,處理請求,訪問其他資源,獲得需要的資訊
service() 方法使用響應物件的方法,將響應傳回Server,最終到達客戶端。service()方法可能啟用其它方法以處理請求,如 doGet() 或 doPost() 或程式設計師自己開發的新的方法。
對於更多的客戶端請求,Server 建立新的請求和響應物件,仍然啟用此 Servlet 的 service() 方法,將這兩個物件作為引數傳遞給它。如此重複以上的迴圈,但無需再次呼叫 init() 方法。一般 Servlet 只初始化一次(只有一個物件),當 Server 不再需要 Servlet 時(一般當 Server 關閉時),Server 呼叫 Servlet 的 destroy() 方法。
工作模式
編輯
客戶端傳送請求至伺服器
伺服器啟動並呼叫 Servlet,Servlet 根據客戶端請求生成響應內容並將其傳給伺服器
伺服器將響應返回客戶端
比較
編輯
與 Applet 的比較
相似之處:
    它們不是獨立的應用程式,沒有 main() 方法。
    它們不是由使用者或程式設計師呼叫,而是由另外一個應用程式(容器)呼叫。
    它們都有一個生存週期,包含 init() 和 destroy() 方法。
不同之處:
    Applet具有很好的圖形介面(AWT),與瀏覽器一起,在客戶端執行。
    Servlet 則沒有圖形介面,執行在伺服器端。
與 CGI 比較
與傳統的 CGI 和許多其他類似 CGI 的技術相比,Java Servlet 具有更高的效率,更容易使用,功能更強大,具有更好的可移植性,更節省投資。在未來的技術發展過程中,Servlet 有可能徹底取代 CGI。
在傳統的 CGI中,每個請求都要啟動一個新的程序,如果 CGI 程式本身的執行時間較短,啟動程序所需要的開銷很可能反而超過實際執行時間。而在 Servlet 中,每個請求由一個輕量級的 Java 執行緒處理(而不是重量級的作業系統程序)。
在傳統 CGI 中,如果有 N 個併發的對同一 CGI程式的請求,則該CGI程式的程式碼在記憶體中重複裝載了 N 次;而對於 Servlet,處理請求的是 N 個執行緒,只需要一份 Servlet 類程式碼。在效能優化方面,Servlet 也比 CGI 有著更多的選擇。
各個使用者請求被啟用成單個程式中的一個執行緒,而無需建立單獨的程序,這意味著伺服器端處理請求的系統開銷將明顯降低。
    方便
Servlet 提供了大量的實用工具例程,例如自動地解析和解碼 HTML 表單資料、讀取和設定 HTTP頭、處理Cookie、跟蹤會話狀態等。
    功能強大
在Servlet中,許多使用傳統 CGI 程式很難完成的任務都可以輕鬆地完成。例如,Servlet 能夠直接和 Web伺服器互動,而普通的 CGI 程式不能。Servlet 還能夠在各個程式之間共享資料,使得資料庫連線池之類的功能很容易實現。
    可移植性好
Servlet 用 Java 編寫,Servlet API具有完善的標準。因此,為 IPlanet Enterprise Server 寫的 Servlet 無需任何實質上的改動即可移植到 Apache、MicrosoftIIS 或者 WebStar。幾乎所有的主流伺服器都直接或通過外掛支援 Servlet。
    節省投資
不僅有許多廉價甚至免費的 Web 伺服器可供個人或小規模網站使用,而且對於現有的伺服器,如果它不支援 Servlet 的話,要加上這部分功能也往往是免費的(或只需要極少的投資)。
與 JSP 比較
JSP 和 Servlet 的區別到底在應用上有哪些體現,很多人搞不清楚。簡單的說,SUN 首先發展出 Servlet,其功能比較強勁,體系設計也很先進,只是,它輸出 HTML 語句還是採用了老的 CGI 方式,是一句一句輸出,所以,編寫和修改 HTML 非常不方便。
Java Server Pages(JSP)是一種實現普通靜態HTML 和動態 HTML 混合編碼的技術,JSP 並沒有增加任何本質上不能用 Servlet 實現的功能。但是,在 JSP 中編寫靜態HTML 更加方便,不必再用 println語 句來輸出每一行 HTML 程式碼。更重要的是,藉助內容和外觀的分離,頁面製作中不同性質的任務可以方便地分開:比如,由頁面設計者進行 HTML設計,同時留出供 Servlet 程式設計師插入動態內容的空間。
後來 SUN 推出了類似於 ASP 的鑲嵌型的 JSP,把 JSP TAG 鑲嵌到 HTML 語句中,這樣,就大大簡化和方便了網頁的設計和修改。新型的網路語言如 ASP,PHP,JSP 都是鑲嵌型的語言。 這是 JSP 和 Servlet 區別的運作原理層面。
從網路三層結構的角度看 JSP 和 Servlet 的區別,一個網路專案最少分三層:data layer(資料層),business layer(業務層),presentation layer(表現層)。當然也可以更復雜。Servlet 用來寫 business layer 是很強大的,但是對於寫 presentation layer 就很不方便。JSP 則主要是為了方便寫 presentation layer 而設計的。當然也可以寫 business layer。寫慣了 ASP,PHP,CGI的朋友,經常會不自覺的把 presentation layer 和 business layer 混在一起。
根據 SUN 自己的推薦,JSP中應該僅僅存放與 presentation layer 有關的東西,也就是說,只放輸出 HTML 網頁的部分。而所有的資料計算,資料分析,資料庫聯結處理,統統是屬於 business layer,應該放在 Java BEANS 中。通過 JSP 呼叫 Java BEANS,實現兩層的整合。
實際上,微軟前不久推出的 DNA 技術,簡單說,就是 ASP+COM/DCOM 技術。與J SP+BEANS 完全類似,所有的 presentation layer 由 ASP 完成,所有的 business layer 由 COM/DCOM 完成。通過呼叫,實現整合。
為什麼要採用這些元件技術呢?因為單純的 ASP/JSP 語言是非常低效率執行的,如果出現大量使用者點選,純 SCRIPT 語言很快就到達了他的功能上限,而元件技術就能大幅度提高功能上限,加快執行速度。
另外一方面,純 SCRIPT 語言將 presentation layer 和 business layer 混在一起,造成修改不方便,並且程式碼不能重複利用。如果想修改一個地方,經常會牽涉到十幾頁 code,採用元件技術就只改元件就可以了。
綜上所述,Servlet 是一個早期的不完善的產品,寫 business layer 很好,寫 presentation layer 就很臭,並且兩層混雜。
所以,推出JSP+BEAN,用 JSP 寫 presentation layer,用 BEAN 寫 business layer。SUN 自己的意思也是將來用 JSP 替代 Servlet。這是技術更新方面 JSP 和 Servlet 的區別。
可是,這不是說,學了 Servlet 沒用,實際上,你還是應該從 Servlet 入門,再上 JSP,再上 JSP+BEAN。
強調的是:學了JSP,不會用 Java BEAN 並進行整合,等於沒學。大家多花點力氣在 JSP+BEAN 上。
我們可以看到,當 ASP+COM 和 JSP+BEAN 都採用元件技術後,所有的元件都是先進行編譯,並駐留記憶體,然後快速執行。所以,大家經常吹的 Servlet/JSP 先編譯駐記憶體後執行的速度優勢就沒有了。
反之,ASP+COM+IIS+NT 緊密整合,應該會有較大的速度優勢呈現。而且,ASP+COM+IIS+NT 開發效率非常高,雖然bug 很多。
那麼,為什麼還用 JSP+BEAN?因為 Java 實在前途遠大。微軟分拆後,作業系統將群雄並起,應用軟體的開發商必定要找一個通用開發語言進行開發,Java 一統天下的時機就到了。如果微軟分拆順利,從中分出的應用軟體公司將成為 Java 的新領導者。目前的 Java 大頭 SUN 和 IBM 都死氣沉沉,令人失望。希望新公司能注入新活力。不過,新公司很有可能和舊 SUN 展開 Java 標準大戰,雙方各自制定標準,影響 Java 跨平臺。
簡單分析了一下 JSP 和 Servlet 的區別和 Java Web 開發方面的發展。隨著機器速度越來越快,Java 的速度劣勢很快就可以被克服。
新增功能
編輯
Servlet 2.2
:引入了 self-contained Web applications 的概念。
servlet 2.3
2000年10月份出來
Servlet API 2.3中最重大的改變是增加了 filters
Servlet 2.3 增加了 filters 和 filter chains 的功能。引入了 context 和 session listeners 的概念,當 context 或 session 被初始化或者被將要被釋放的時候,和當向 context 或 session 中繫結屬性或解除繫結的時候,可以對類進行監測。
servlet 2.4
2003年11月份出來
Servlet 2.4 加入了幾個引起關注的特性,沒有特別突出的新內容,而是花費了更多的功夫在推敲和闡明以前存在的一些特性上,對一些不嚴謹的地方進行了校驗。
Servlet 2.4 增加了新的最低需求,新的監測 request 的方法,新的處理 response 的方法,新的國際化支援,RequestDispatcher 的幾個處理,新的 request listener 類,session 的描述,和一個新的基於 Schema 的並擁有 J2EE 元素的釋出描述符。這份文件規範全面而嚴格的進行了修訂,除去了一些可能會影響到跨平臺釋出的模糊不清的因素。總而言之,這份規範增加了四個新類,七個新方法,一個新常量,不再推薦使用一個類。
注意:改為 Schema 後主要加強了兩項功能:
(1) 元素不依照順序設定;
(2) 更強大的驗證機制。
主要體現在:
a.檢查元素的值是否為合法的值
b.檢查元素的值是否為合法的文字字元或者數字字元
c.檢查 Servlet, Filter, EJB-ref 等等元素的名稱是否唯一
2.新增 Filter 四種設定:REQUEST、FORWARD、INCLUDE 和 ERROR。
3.新增 Request Listener、Event和Request Attribute Listener、Event。
4.取消 SingleThreadModel 介面。當 Servlet 實現 SingleThreadModel 介面時,它能確保同時間內,只能有一個 thread 執行此 Servlet。
5.可以為Servlet。
6.ServletRequest介面新增一些方法。
public String getLocalName();
public String getLocalAddr();
public int getLocalPort();
public int getRemotePort()
Servlet 2.5
2005 年 9 月釋出 Servlet 2.5
Servlet 2.5 一些變化的介紹:
1) 基於最新的 J2SE 5.0 開發的。
2) 支援 annotations 。
3) web.xml 中的幾處配置更加方便。
4) 去除了少數的限制。
5) 優化了一些例項
Servlet 的各個版本對監聽器的變化有:
(1) Servlet 2.2 和 jsp1.1
新增Listener:HttpSessionBindingListener
新增Event: HttpSessionBindingEvent
(2) Servlet 2.3 和 jsp1.2
新增Listener:ServletContextListener,ServletContextAttributeListener
,HttpSessionListener,HttpSessionActivationListener,HttpSessionAttributeListener
新增Event: ServletContextEvent,ServletContextAttributeEvent,HttpSessionEvent
(3) Servlet 2.4 和 jsp2.0
新增Listener:ServletRequestListener,ServletRequestAttribureListener
新增Event: ServletRequestEvent,ServletRequestAttributeEvent
Servlet 3.0
Servlet 3.0 作為 Java EE 6 規範體系中一員[1] ,隨著 Java EE 6 規範一起釋出。該版本在前一版本(Servlet 2.5)的基礎上提供了若干新特性用於簡化 Web 應用的開發和部署。其中有幾項特性的引入讓開發者感到非常興奮,同時也獲得了 Java 社群的一片讚譽之聲:[2]

非同步處理支援:有了該特性,Servlet 執行緒不再需要一直阻塞,直到業務處理完畢才能再輸出響應,最後才結束該 Servlet 執行緒。在接收到請求之後,Servlet 執行緒可以將耗時的操作委派給另一個執行緒來完成,自己在不生成響應的情況下返回至容器。針對業務處理較耗時的情況,這將大大減少伺服器資源的佔用,並且提高併發處理速度。[2]

新增的註解支援:該版本新增了若干註解,用於簡化 Servlet、過濾器(Filter)和監聽器(Listener)的宣告,這使得 web.xml 部署描述檔案從該版本開始不再是必選的了。[2]

可插性支援:熟悉 Struts2 的開發者一定會對其通過外掛的方式與包括 Spring 在內的各種常用框架的整合特性記憶猶新。將相應的外掛封裝成 JAR 包並放在類路徑下,Struts2 執行時便能自動載入這些外掛。現在 Servlet 3.0 提供了類似的特性,開發者可以通過外掛的方式很方便的擴充已有 Web 應用的功能,而不需要修改原有的應用。[2]
Servlet 4.0草案
從3.1到4.0將是對Servlet 協議的一次大改動,而改動的關鍵之處在於對HTTP/2的支援。HTTP2將是是繼上世紀末HTTP1.1協議規範化以來首個HTTP協議新版本,相對於HTTP1.1,HTTP2將帶來許多的增強。在草案提議中,Shing Wai列舉出了一些HTTP2的新特性,而這些特性也正是他希望在Servlet 4.0 API中實現並暴露給使用者的新功能,這些新特性如下:[3]
1.請求/響應複用(Request/Response multiplexing)
  2.流的優先順序(Stream Prioritization)
  3.伺服器推送(Server Push)
  4.HTTP1.1升級(Upgrade from HTTP 1.1)[3]
規範
編輯
1.簡化開發
2.便於部署
3.支援 Web2.0 原則
為了簡化開發流程,Servlet 3.0 引入了註解(annotation),這使得 web 部署描述符 web.xml 不再是必須的選擇。
Pluggability可插入性
當使用任何第三方的框架,如 Struts,JSF 或 Spring,我們都需要在 web.xml 中新增對應的 Servlet 的入口。這使得 web 描述符笨重而難以維護。Servlet3.0 的新的可插入特性使得 web 應用程式模組化而易於維護。通過 web fragment 實現的可插入性減輕了開發人員的負擔,不需要再在 web.xml 中配置很多的 Servlet 入口。
Asynchronous Processing 非同步處理
另外一個顯著的改變就是 Servlet 3.0 支援非同步處理,這對 AJAX 應用程式非常有用。當一個 Servlet 建立一個執行緒來處理某些請求的時候,如查詢資料庫或訊息連線,這個執行緒要等待直到獲得所需要的資源才能夠執行其他的操作。非同步處理通過執行執行緒執行其他的操作來避免了這種阻塞。
Apart from the features mentioned here, several other enhancements have been made to the existing API. The sections towards the end of the article will explore these features one by one in detail.
除了這些新特性之外, Servlet 3.0對已有的 API 也做了一些改進,在本文的最後我們會做介紹。
Annotations in Servlet Servlet中使用註解
Servlet 3.0 的一個主要的改變就是支援註解。使用註解來定義 Servlet 和 filter 使得我們不用在 web.xml 中定義相應的入口。
@WebServlet
@WebServlet 用來定義 web 應用程式中的一個 Servlet。這個註解可以應用於繼承了 HttpServlet。這個註解有多個屬性,例如 name,urlPattern, initParams,我們可以使用者的屬性來定義 Servlet 的行為。urlPattern 屬性是必須指定的。
程式設計介面
編輯
HTTPServlet 使用一個 HTML 表單來發送和接收資料。要建立一個 HTTPServlet,請擴充套件 HttpServlet 類, 該類是用專門的方法來處理 HTML 表單的 GenericServlet 的一個子類。 HTML 表單是由 和 標記定義的。表單中典型地包含輸入欄位(如文字輸入欄位、複選框、單選按鈕和選擇列表)和用於提交資料的按鈕。當提交資訊時,它們還指定伺服器應執行哪一個Servlet(或其它的程式)。 HttpServlet 類包含 init()、destroy()、service() 等方法。其中 init() 和 destroy() 方法是繼承的。
(1) init() 方法
在 Servlet 的生命期中,僅執行一次 init() 方法。它是在伺服器裝入 Servlet 時執行的。 可以配置伺服器,以在啟動伺服器或客戶機首次訪問 Servlet 時裝入 Servlet。 無論有多少客戶機訪問 Servlet,都不會重複執行 init() 。
預設的 init() 方法通常是符合要求的,但也可以用定製 init() 方法來覆蓋它,典型的是管理伺服器端資源。 例如,可能編寫一個定製 init() 來只用於一次裝入 GIF 影象,改進 Servlet 返回 GIF 影象和含有多個客戶機請求的效能。另一個示例是初始化資料庫連線。預設的 init() 方法設定了 Servlet 的初始化引數,並用它的 ServletConfig 物件引數來啟動配置, 因此所有覆蓋 init() 方法的 Servlet 應呼叫 super.init() 以確保仍然執行這些任務。在呼叫 service() 方法之前,應確保已完成了 init() 方法。
(2) service() 方法
service() 方法是 Servlet 的核心。每當一個客戶請求一個HttpServlet 物件,該物件的service() 方法就要被呼叫,而且傳遞給這個方法一個”請求”(ServletRequest)物件和一個”響應”(ServletResponse)物件作為引數。 在 HttpServlet 中已存在 service() 方法。預設的服務功能是呼叫與 HTTP 請求的方法相應的 do 功能。例如, 如果 HTTP 請求方法為 GET,則預設情況下就呼叫 doGet() 。Servlet 應該為 Servlet 支援的 HTTP 方法覆蓋 do 功能。因為 HttpServlet.service() 方法會檢查請求方法是否呼叫了適當的處理方法,不必要覆蓋 service() 方法。只需覆蓋相應的 do 方法就可以了。
Servlet 的響應可以是下列幾種型別:
一個輸出流,瀏覽器根據它的內容型別(如 text/html)進行解釋。
一個 HTTP 錯誤響應,重定向到另一個 URL、servlet、JSP。
(3) doGet() 方法
當一個客戶通過 HTML 表單發出一個 HTTP GET 請求或直接請求一個 URL 時,doGet() 方法被呼叫。與 GET 請求相關的引數新增到 URL 的後面,並與這個請求一起傳送。當不會修改伺服器端的資料時,應該使用 doGet() 方法。
(4) doPost() 方法
當一個客戶通過 HTML 表單發出一個 HTTP POST 請求時,doPost() 方法被呼叫。與 POST 請求相關的引數作為一個單獨的 HTTP 請求從瀏覽器傳送到伺服器。當需要修改伺服器端的資料時,應該使用 doPost() 方法。
(5) destroy() 方法
destroy() 方法僅執行一次,即在伺服器停止且卸裝 Servlet 時執行該方法。典型的,將 Servlet 作為伺服器程序的一部分來關閉。預設的 destroy() 方法通常是符合要求的,但也可以覆蓋它,典型的是管理伺服器端資源。例如,如果 Servlet 在執行時會累計統計資料,則可以編寫一個 destroy() 方法,該方法用於在未裝入 Servlet 時將統計數字儲存在檔案中。另一個示例是關閉資料庫連線。
當伺服器卸裝 Servlet 時,將在所有 service() 方法呼叫完成後,或在指定的時間間隔過後呼叫 destroy() 方法。一個 Servlet 在執行 service() 方法時可能會產生其它的執行緒,因此請確認在呼叫 destroy() 方法時,這些執行緒已終止或完成。
(6) getServletConfig() 方法
getServletConfig() 方法返回一個 ServletConfig 物件,該物件用來返回初始化引數和 ServletContext。ServletContext 介面提供有關 servlet 的環境資訊。
(7) getServletInfo() 方法
getServletInfo() 方法是一個可選的方法,它提供有關 servlet 的資訊,如作者、版本、版權。
當伺服器呼叫 sevlet 的 service()、doGet() 和 doPost() 這三個方法時,均需要 “請求”和“響應”物件作為引數。“請求”物件提供有關請求的資訊,而“響應”物件提供了一個將響應資訊返回給瀏覽器的一個通訊途徑。
javax.servlet 軟體包中的相關類為 ServletResponse 和 ServletRequest,而 javax.servlet.http 軟體包中的相關類為 HttpServletRequest 和 HttpServletResponse。Servlet 通過這些物件與伺服器通訊並最終與客戶端通訊。Servlet 能通過呼叫”請求”物件的方法獲知客戶端環境,伺服器環境的資訊和所有由客戶機提供的資訊。Servlet 可以呼叫“響應”物件的方法傳送響應,該響應是準備發回客戶端的。
常見容器
編輯
Tomcat, Jetty, resin, Oracle Application server, WebLogic Server, Glassfish, Websphere, JBoss 等等。(提供了 Servlet 功能的伺服器,叫做 Servlet 容器。對 web 程式來說,Servlet 容器的作用就相當於桌面程式裡作業系統的作用,都是提供一些程式設計基礎設施)
建議
編輯
在 Web 應用程式中,一個 Servlet 在一個時刻可能被多個使用者同時訪問。這時 Web 容器將為每個使用者建立一個執行緒來執行 Servlet。如果 Servlet 不涉及共享資源的問題,不必關心多執行緒問題。但如果 Servlet 需要共享資源,需要保證 Servlet 是執行緒安全的。
下面是編寫執行緒安全的 Servlet 的一些建議:
(1)用方法的區域性變數儲存請求中的專有資料。對方法中定義的區域性變數,進入方法的每個執行緒都有自己的一份方法變數拷貝。任何執行緒都不會修改其他執行緒的區域性變數。如果要在不同的請求之間共享資料,應該使用會話來共享這類資料。
(2)只用 Servlet的成員變數來存放那些不會改變的資料。有些資料在 Servlet 生命週期中不發生任何變化,通常是在初始時確定的,這些資料可以使用成員變數儲存,如資料庫連線名稱、其他資源的路徑等。
(3)對可能被請求修改的成員變數同步。有時資料成員變數或者環境屬性可能被請求修改。當訪問這些資料時應該對它們同步,以避免多個執行緒同時修改這些資料。
(4)如果 Servlet 訪問外部資源,那麼需要同步訪問這些資源。例如,假設 Servlet 要從檔案中讀寫資料。當一個執行緒讀寫一個檔案時,其他執行緒也可能正在讀寫這個檔案。檔案訪問本身不是執行緒安全的,所以必須編寫同步訪問這些資源的程式碼。在編寫執行緒安全的 Servlet 時,下面兩種方法是不應該使用的:
(1)在 Servlet API 中提供了一個 SingleThreadModel 介面,實現這個介面的 Servlet 在被多個客戶請求時一個時刻只有一個執行緒執行。這個介面已被標記不推薦使用。
(2)對 doGet() 或doPost() 方法同步。如果必須在 Servlet 中使用同步程式碼,應儘量在最小的程式碼塊範圍上進行同步。同步程式碼越小,Servlet 執行得才越好。[4]

servlet在spring中載入的順序

2013-05-22 19:27:50| 分類: java |舉報|字號 訂閱

下載LOFTER我的照片書 |
spring中,servlet執行init方法的時機。

web.xml中常用的servlet定義:

assembler
com.branchitech.app.startup.AppStartupWrappedServlet

targetProxyClass
org.springframework.web.servlet.DispatcherServlet

2


assembler
*.do

其中2標籤定義了servlet載入的順序:

1)load-on-startup元素標記容器是否在啟動的時候就載入這個servlet(例項化並呼叫其init()方法)。

2)它的值必須是一個整數,表示servlet應該被載入的順序

2)當值為0或者大於0時,表示容器在應用啟動時就載入並初始化這個servlet;

3)當值小於0或者沒有指定時,則表示容器在該servlet被選擇時才會去載入。

4)正數的值越小,該servlet的優先順序越高,應用啟動時就越先載入。

5)當值相同時,容器就會自己選擇順序來載入

在執行時,Servlet容器的類載入器先載入classes目錄下的類,再載入lib目錄下的JAR檔案中的類。因此,如果兩個目錄下存在同名的類,classes目錄下的類具有優先權。

我們注意到Tomcat的安裝目錄下也有一個lib目錄,這個與Web應用中的lib目錄的區別在於:
Tomcat的lib子目錄:存放的JAR檔案不僅能被Tomcat訪問,還能被所有在Tomcat中釋出的JavaWeb應用訪問。
JavaWeb應用的lib子目錄:存放的JAR檔案只能被當前JavaWeb應用訪問。
假如Tomcat類載入器要載入一個MyClass的類,它會按照以下先後順序到各個目錄中去查詢MyClass的class檔案,直到找到為止,如果所有目錄中都不存在MyClass.class的檔案,則會丟擲異常:
1、在JavaWeb應用的WEB-INF/classes中查詢MyClass.class檔案。
2、在JavaWeb應用的 WEB-INF/lib目錄下的JAR檔案中查詢MyClass.class檔案。
3、在Tomcat的lib子目錄下直接查詢MyClass.class檔案。
4、在Tomcat的lib子目錄下JAR的檔案中查詢MyClass.class檔案。

1、裝載並例項化Servlet(在整個生命週期中Servlet例項只有一個)

分為兩種裝載方式:

a) 延遲裝載(預設方式)

  當客戶端發起一個請求第一次去訪問Servlet時,容器會將Servlet裝載進虛擬機器並例項化,第二次以後去訪問同一個Servlet時容器就不會再去裝載並例項化。

b) 預先裝載

  當Web Server啟動,容器在裝載Web應用的時候會將Servlet裝載進虛擬機器並例項化。

這種方式必須在web.xml中描述:

<servlet>

...

    <load-on-startup>

        number

    </load-on-startup>

</servlet>

number<0: 採用延遲裝載

number>=0: 採用預先裝載

number越小越先被裝載,number越大越晚被裝載

number=0最晚被裝載

一,servlet容器如何同時處理多個請求。
Servlet採用多執行緒來處理多個請求同時訪問,Servelet容器維護了一個執行緒池來服務請求。
執行緒池實際上是等待執行程式碼的一組執行緒叫做工作者執行緒(Worker Thread),Servlet容器使用一個排程執行緒來管理工作者執行緒(Dispatcher Thread)。
當容器收到一個訪問Servlet的請求,排程者執行緒從執行緒池中選出一個工作者執行緒,將請求傳遞給該執行緒,然後由該執行緒來執行Servlet的service方法。
當這個執行緒正在執行的時候,容器收到另外一個請求,排程者執行緒將從池中選出另外一個工作者執行緒來服務新的請求,容器並不關係這個請求是否訪問的是同一個Servlet還是另外一個Servlet。
當容器同時收到對同一Servlet的多個請求,那這個Servlet的service方法將在多執行緒中併發的執行。

二,Servlet容器預設採用單例項多執行緒的方式來處理請求,這樣減少產生Servlet例項的開銷,提升了對請求的響應時間。對於Tomcat可以在server.xml中通過元素設定執行緒池中執行緒的數目。
就實現來說:
排程者執行緒類所擔負的責任如其名字,該類的責任是排程執行緒,只需要利用自己的屬性完成自己的責任。所以該類是承擔了責任的,並且該類的責任又集中到唯一的單體物件中。
而其他物件又依賴於該特定物件所承擔的責任,我們就需要得到該特定物件。那該類就是一個單例模式的實現了。
三,如何開發執行緒安全的Servlet
1,變數的執行緒安全:這裡的變數指欄位和共享資料(如表單引數值)。
a,將 引數變數 本地化。多執行緒並不共享區域性變數.所以我們要儘可能的在servlet中使用區域性變數。
例如:String user = “”;
user = request.getParameter(“user”);
b,使用同步塊Synchronized,防止可能非同步呼叫的程式碼塊。這意味著執行緒需要排隊處理。
在使用同板塊的時候要儘可能的縮小同步程式碼的範圍,不要直接在sevice方法和響應方法上使用同步,這樣會嚴重影響效能。

2,屬性的執行緒安全:ServletContext,HttpSession,ServletRequest物件中屬性
ServletContext:(執行緒是不安全的)
ServletContext是可以多執行緒同時讀/寫屬性的,執行緒是不安全的。要對屬性的讀寫進行同步處理或者進行深度Clone()。
所以在Servlet上下文中儘可能少量儲存會被修改(寫)的資料,可以採取其他方式在多個Servlet中共享,比方我們可以使用單例模式來處理共享資料。
HttpSession:(執行緒是不安全的)
HttpSession物件在使用者會話期間存在,只能在處理屬於同一個Session的請求的執行緒中被訪問,因此Session物件的屬性訪問理論上是執行緒安全的。
當用戶開啟多個同屬於一個程序的瀏覽器視窗,在這些視窗的訪問屬於同一個Session,會出現多次請求,需要多個工作執行緒來處理請求,可能造成同時多執行緒讀寫屬性。
這時我們需要對屬性的讀寫進行同步處理:使用同步塊Synchronized和使用讀/寫器來解決。
ServletRequest:(執行緒是安全的)
對於每一個請求,由一個工作執行緒來執行,都會建立有一個新的ServletRequest物件,所以ServletRequest物件只能在一個執行緒中被訪問。ServletRequest是執行緒安全的。
注意:ServletRequest物件在service方法的範圍內是有效的,不要試圖在service方法結束後仍然儲存請求物件的引用。
3,使用同步的集合類:
使用Vector代替ArrayList,使用Hashtable代替HashMap。
4,不要在Servlet中建立自己的執行緒來完成某個功能。
Servlet本身就是多執行緒的,在Servlet中再建立執行緒,將導致執行情況複雜化,出現多執行緒安全問題。
5,在多個servlet中對外部物件(比方檔案)進行修改操作一定要加鎖,做到互斥的訪問。
四,SingleThreadModel介面
javax.servlet.SingleThreadModel介面是一個標識介面,如果一個Servlet實現了這個介面,那Servlet容器將保證在一個時刻僅有一個執行緒可以在給定的servlet例項的service方法中執行。將其他所有請求進行排隊。
伺服器可以使用多個例項來處理請求,代替單個例項的請求排隊帶來的效益問題。伺服器建立一個Servlet類的多個Servlet例項組成的例項池,對於每個請求分配Servlet例項進行響應處理,之後放回到例項池中等待下此請求。這樣就造成併發訪問的問題。
此時,區域性變數(欄位)也是安全的,但對於全域性變數和共享資料是不安全的,需要進行同步處理。而對於這樣多例項的情況SingleThreadModel介面並不能解決併發訪問問題。

SingleThreadModel介面在servlet規範中已經被廢棄了。

怎樣理解Servlet的單例項多執行緒 2013-05-14 15:34:39
分類: Java
首先明確:Servlet是單例項的,即對於同一種業務請求只有一個是例項。不同的業務請求可以通過分發來產生多個例項。
其次:單例項的原因我想是因為單例項足可以處理某一個請求,就像ibatis的Querydao、UpdateDao一樣都是單例項的。
再次:為什麼單例項足可以處理某一個請求,因為Servlet是單例項多執行緒的。
http://hiyachen.cublog.cn [email protected]
先看一段程式碼:
package hiya.test;

public class Servlet {
private static Servlet instance=new Servlet();
private Servlet(){

}
public static Servlet getInstance(){
return instance;
}
public void services(){
System.out.println(“do something”);
}

static class Client extends Thread{
private Servlet servlet;
public Client(Servlet servlet){
this.servlet=servlet;
}
public void run(){
servlet.services();
//System.out.println(“do something”);
}
}

public static void main(){
Servlet servlet=Servlet.getInstance();
for(int i=0;i<10;i++){
Client client=new Client(servlet);
client.start();
}
}
}

這是單例項多執行緒的實現程式碼。(真想把jdk原始碼貼出來。)
servlet單例項多執行緒處理原理:
servlet中的init方法只有在啟動(例如web容器啟動,要看loadOnStartup的設定)的時候呼叫,也就是隻初始化一次,這就是單例項。
servlet在處理請求的時候 呼叫的是service方法,這個方法可以處理多個客戶端的請求。
具體訪問時:
JSP 在web容器中”翻譯成servlet”由容器執行,web 容器本身就是提供的多執行緒,A,B,C 3個訪問,建立3個獨立的執行緒組,然後執行一個servlet。依次執行。
這就解決了多使用者同一例項實行的困惑。
要注意幾點:
1:servlet首先不是現成執行緒的。

2:Servlet體系結構是建立在Java多執行緒機制之上的,它的生命週期是由Web容器負責的。

Servlet容器會自動使用執行緒池等技術來支援系統的執行
3:設定jsp:<%@ page isThreadSafe=”false”%>來實現單執行緒。

當你需要保證資料一致性的時候,必須自己處理執行緒安全問題時可以考慮單執行緒。

Servlet多執行緒同步問題(important)
最主要的原因之一是:Servlet是單例模式,一個Servlet給所有的使用者提供服務,有可能造成資源混亂的模式

Servlet的多執行緒同步問題:Servlet本身是單例項的,這樣當有多個使用者同時訪問某個Servlet時,會訪問該唯一的Servlet例項中的成員變數,如果對成員變數進行寫入操作,那就會導致Servlet的多執行緒問題,即資料不一致。

解決同步問題的方案:
1、去除例項變數,使用區域性變數(最好的)
2、使用synchronized{}

1、Servlet/JSP技術和ASP、PHP等相比,由於其多執行緒執行而具有很高的執行效率。

2、由於Servlet/JSP預設是以多執行緒模式執行的,所以,在編寫程式碼時需要非常細緻地考慮多執行緒的同步問題。

3、如果在編寫Servlet/JSP程式時不注意到多執行緒同步的問題,這往往造成編寫的程式在少量使用者訪問時沒有任何問題,而在併發使用者上升到一定值時,就會經常出現一些莫明其妙的問題,對於這類隨機性的問題除錯難度也很大。

例題:

public class ThreadServlet extends HttpServlet{
private String username;
因為是成員變數,又是單例的,所以這個username共享資源
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
username = req.getParameter(“username”);
try{
Thread.sleep(4000);
}catch(Exception e){
e.printStackTrace();
}
req.setAttribute(“username”, this.username);
req.getRequestDispatcher(“counter.jsp”).forward(req, resp);
}

}

填表單的時候一個填的是baliang,一個是banjin,結果卻都為banjin,這就是多執行緒資源共享問題!~

JSP/Servlet的多執行緒原理:
1.servelet就是一個CGI,但比傳統的CGI要快得過
傳統CGI是多程序的,servlet是多執行緒的
以多執行緒方式執行可大大降低對系統的資源需求,提高 系統的併發量及響應時間.
JSP/Servlet容器預設是採用單例項多執行緒(這是造成執行緒安全的主因)方式處理多個請求的:
當客戶端第一次請求某一個JSP檔案時(有的servlet是隨容器啟動就startup):
服務端把該JSP編譯成一個CLASS檔案
並建立一個該類的例項
然後建立一個執行緒處理CLIENT端的請求。
多請求,多執行緒:
如果有多個客戶端同時請求該JSP檔案,則服務端會建立多個執行緒。每個客戶端請求對應一個執行緒。

servlet 的執行緒安全
servlet裡的 例項變數
servlet裡的例項變數,是被所有執行緒共享的,所以不是執行緒安全的.
servlet方法裡的區域性變數
因為每個執行緒都有它自己的堆疊空間,方法內區域性變數儲存在這個執行緒堆疊空間內,
且引數傳入方法是按傳值volue copy的方式
所以是執行緒安全的
Application物件
在container執行期間,被整個系統內所有使用者共同使用,所以不是執行緒安全 的
ServletContext物件
ServletContext是可以多執行緒同時讀/寫屬性的,執行緒是不安全的。
struts2 的ServletContext採用的是TreadLocal模式,是執行緒安全的
HttpServletRequest物件和HttpServletResponse物件
每一個請求,由一個工作執行緒來執行,都會建立有一對新的ServletRequest物件和ServletResponse,然後傳入service()方法內
所以每個ServletRequest物件對應每個執行緒,而不是多執行緒共享,是執行緒安全的。所以不用擔心request引數和屬性的執行緒安全性
HttpSession
Session物件在使用者session期間存在,只能在屬於同一個SessionID的請求的執行緒中被訪問,因此Session物件的理論上是執行緒安全的。
(當用戶開啟多個同屬於一個程序的瀏覽器視窗(常見的彈出視窗),在這些視窗的訪問屬於同一個Session,會出現多次請求,需要多個工作執行緒來處理請求,這時就有可能的出現執行緒安全問題)

servlet 儘量用方法內變數,就一定執行緒安全麼? 區域性變數的資料也來自request物件或session物件啊,它們執行緒安全麼?
servletRequest 執行緒是安全的
因為:每個 request 都會建立一個新執行緒,每個新執行緒,容器又都會建立一對servletRequest和servletResponse物件(這是servlet基本原理)
所以servletRequest物件和servletResponse物件只在一個執行緒內被建立,存在,被訪問

常見的執行緒安全的解決辦法:
1.使用方法內區域性變數
是因為各執行緒有自己堆疊空間,儲存區域性變數
方法引數傳入,多采用傳值(volue copy)傳入方法內

2.對操作共享資源的語句,方法,物件, 使用同步
比如寫入磁碟檔案,採用同步鎖,但建議儘量用同步程式碼塊,不要用同步方法

3.使用同步的集合類
使用Vector代替ArrayList
使用Hashtable代替HashMap。

4.不要在 Servlet中再建立自己的執行緒來完成某個功能。
Servlet本身就是多執行緒的,在Servlet中再建立執行緒,將導致執行情況複雜化

Servlet體系結構是建立在Java多執行緒機制之上的,它的生命週期是由Web容器負責的。當客戶端第一次請求某個Servlet時,Servlet容器將會根據web.xml配置檔案例項化這個Servlet類。當有新的客戶端請求該Servlet時,一般不會再例項化該Servlet類,也就是有多個執行緒在使用這個例項。 這樣,當兩個或多個執行緒同時訪問同一個Servlet時,可能會發生多個執行緒同時訪問同一資源的情況,資料可能會變得不一致。所以在用Servlet構建的Web應用時如果不注意執行緒安全的問題,會使所寫的Servlet程式有難以發現的錯誤。

例項變數不正確的使用是造成Servlet執行緒不安全的主要原因。下面針對該問題給出了三種解決方案並對方案的選取給出了一些參考性的建議。

  1、實現 SingleThreadModel 介面

  該介面指定了系統如何處理對同一個Servlet的呼叫。如果一個Servlet被這個介面指定,那麼在這個Servlet中的service方法將不會有兩個執行緒被同時執行,當然也就不存線上程安全的問題。這種方法只要將前面的Concurrent Test類的類頭定義更改為:

Public class Concurrent Test extends HttpServlet implements SingleThreadModel {
…………
}
  2、同步對共享資料的操作

  使用synchronized 關鍵字能保證一次只有一個執行緒可以訪問被保護的區段,在本論文中的Servlet可以通過同步塊操作來保證執行緒的安全。同步後的程式碼如下:

…………
Public class Concurrent Test extends HttpServlet { …………
Username = request.getParameter (“username”);
Synchronized (this){
Output = response.getWriter ();
Try {
Thread. Sleep (5000);
} Catch (Interrupted Exception e){}
output.println(“使用者名稱:”+Username+”
“);
}
}
}
  3、避免使用例項變數

  本例項中的執行緒安全問題是由例項變數造成的,只要在Servlet裡面的任何方法裡面都不使用例項變數,那麼該Servlet就是執行緒安全的。

  修正上面的Servlet程式碼,將例項變數改為區域性變數實現同樣的功能,程式碼如下:

……
Public class Concurrent Test extends HttpServlet {public void service (HttpServletRequest request, HttpServletResponse
Response) throws ServletException, IOException {
Print Writer output;
String username;
Response.setContentType (“text/html; charset=gb2312”);
……
}
}
  對上面的三種方法進行測試,可以表明用它們都能設計出執行緒安全的Servlet程式。但是,如果一個Servlet實現了SingleThreadModel介面,Servlet引擎將為每個新的請求建立一個單獨的Servlet例項,這將引起大量的系統開銷。SingleThreadModel在Servlet2.4中已不再提倡使用;同樣如果在程式中使用同步來保護要使用的共享的資料,也會使系統的效能大大下降。這是因為被同步的程式碼塊在同一時刻只能有一個執行緒執行它,使得其同時處理客戶請求的吞吐量降低,而且很多客戶處於阻塞狀態。另外為保證主存內容和執行緒的工作記憶體中的資料的一致性,要頻繁地重新整理快取,這也會大大地影響系統的效能。所以在實際的開發中也應避免或最小化 Servlet 中的同步程式碼;在Serlet中避免使用例項變數是保證Servlet執行緒安全的最佳選擇。從Java 記憶體模型也可以知道,方法中的臨時變數是在棧上分配空間,而且每個執行緒都有自己私有的棧空間,所以它們不會影響執行緒的安全。

補充:

servlet存在的多執行緒問題
例項變數: 例項變數是在堆中分配的,並被屬於該例項的所有執行緒共享,所以不是執行緒安全的.
JSP系統提供的8個類變數:
JSP中用到的OUT,REQUEST,RESPONSE,SESSION,CONFIG,PAGE,PAGECONXT是執行緒安全的,APPLICATION在整個系統內被使用,所以不是執行緒安全的.
區域性變數: 區域性變數在堆疊中分配,因為每個執行緒都有它自己的堆疊空間,所以是執行緒安全的.
靜態類: 靜態類不用被例項化,就可直接使用,也不是執行緒安全的.
外部資源: 在程式中可能會有多個執行緒或程序同時操作同一個資源(如:多個執行緒或程序同時對一個檔案進行寫操作).

此時也要注意同步問題. 使它以單執行緒方式執行,這時,仍然只有一個例項,所有客戶端的請求以序列方式執行。這樣會降低系統的效能
對於存線上程不安全的類,如何避免出現執行緒安全問題:
1、採用synchronized同步。缺點就是存在堵塞問題。
2、使用ThreadLocal(實際上就是一個HashMap),這樣不同的執行緒維護自己的物件,執行緒之間相互不干擾。

ThreadLocal的設計
首先看看ThreadLocal的介面:
Object get() ; // 返回當前執行緒的執行緒區域性變數副本 protected Object
initialValue(); // 返回該執行緒區域性變數的當前執行緒的初始值
void set(Object value); // 設定當前執行緒的執行緒區域性變數副本的值
  ThreadLocal有3個方法,其中值得注意的是initialValue(),該方法是一個protected
的方法,顯然是為了子類重寫而特意實現的。該方法返回當前執行緒在該執行緒區域性變數的初始
值,這個方法是一個延遲呼叫方法,在一個執行緒第1次呼叫get()或者set(Object)時才執行
,並且僅執行1次。ThreadLocal中的確實實現直接返回一個null:
protected Object initialValue() { return null; }
  ThreadLocal是如何做到為每一個執行緒維護變數的副本的呢?其實實現的思路很簡單,
在ThreadLocal類中有一個Map,用於儲存每一個執行緒的變數的副本。比如下面的示例實現:

public class ThreadLocal
{
 private Map values = Collections.synchronizedMap(new HashMap());
 public Object get()
 {
  Thread curThread = Thread.currentThread();
  Object o = values.get(curThread);
  if (o == null && !values.containsKey(curThread))
  {
   o = initialValue();
   values.put(curThread, o);
  }
  return o;
 }

 public void set(Object newValue)
 {
  values.put(Thread.currentThread(), newValue);
 }

 public Object initialValue()
 {
  return null;
 }
}

  當然,這並不是一個工業強度的實現,但JDK中的ThreadLocal的實現總體思路也類似於此。
ThreadLocal的使用
  如果希望執行緒區域性變數初始化其它值,那麼需要自己實現ThreadLocal的子類並重寫該
方法,通常使用一個內部匿名類對ThreadLocal進行子類化,比如下面的例子,SerialNum類
為每一個類分配一個序號:
public class SerialNum
{
 // The next serial number to be assigned
 private static int nextSerialNum = 0;
 private static ThreadLocal serialNum = new ThreadLocal()
 {
  protected synchronized Object initialValue()
  {
   return new Integer(nextSerialNum++);
  }
 };

 public static int get()
 {
  return ((Integer) (serialNum.get())).intValue();
 }
}

  SerialNum類的使用將非常地簡單,因為get()方法是static的,所以在需要獲取當前線
程的序號時,簡單地呼叫:

int serial = SerialNum.get(); 即可。
  線上程是活動的並且ThreadLocal物件是可訪問的時,該執行緒就持有一個到該執行緒區域性
變數副本的隱含引用,當該執行緒執行結束後,該執行緒擁有的所以執行緒區域性變數的副本都將失
效,並等待垃圾收集器收集。
ThreadLocal與其它同步機制的比較
  ThreadLocal和其它同步機制相比有什麼優勢呢?ThreadLocal和其它所有的同步機制都
是為了解決多執行緒中的對同一變數的訪問衝突,在普通的同步機制中,是通過物件加鎖來實
現多個執行緒對同一變數的安全訪問的。這時該變數是多個執行緒共享的,使用這種同步機制需
要很細緻地分析在什麼時候對變數進行讀寫,什麼時候需要鎖定某個物件,什麼時候釋放該
物件的鎖等等很多。所有這些都是因為多個執行緒共享了資源造成的。ThreadLocal就從另一
個角度來解決多執行緒的併發訪問,ThreadLocal會為每一個執行緒維護一個和該執行緒繫結的變
量的副本,從而隔離了多個執行緒的資料,每一個執行緒都擁有自己的變數副本,從而也就沒有
必要對該變數進行同步了。ThreadLocal提供了執行緒安全的共享物件,在編寫多執行緒程式碼時
,可以把不安全的整個變數封裝進ThreadLocal,或者把該物件的特定於執行緒的狀態封裝進
ThreadLocal。
  由於ThreadLocal中可以持有任何型別的物件,所以使用ThreadLocal get當前執行緒的值
是需要進行強制型別轉換。但隨著新的Java版本(1.5)將模版的引入,新的支援模版引數
的ThreadLocal類將從中受益。也可以減少強制型別轉換,並將一些錯誤檢查提前到了編
譯期,將一定程度地簡化ThreadLocal的使用。
總結
當然ThreadLocal並不能替代同步機制,兩者面向的問題領域不同。同步機制是為了同
步多個執行緒對相同資源的併發訪問,是為了多個執行緒之間進行通訊的有效方式;而
ThreadLocal是隔離多個執行緒的資料共享,從根本上就不在多個執行緒之間共享資源(變數)
,這樣當然不需要對多個執行緒進行同步了。所以,如果你需要進行多個執行緒之間進行通訊,
則使用同步機制;如果需要隔離多個執行緒之間的共享衝突,可以使用ThreadLocal,這將極
大地簡化你的程式,使程式更加易讀、簡潔。

ThreadLocal常見用途:
存放當前session使用者
存放一些context變數,比如webwork的ActionContext
存放session,比如spring hibernate orm的session

例子:用 ThreadLocal 實現每執行緒 Singleton
執行緒區域性變數常被用來描繪有狀態“單子”(Singleton) 或執行緒安全的共享物件,或者是通過把不安全的整個變數封裝進 ThreadLocal,或者是通過把物件的特定於執行緒的狀態封裝進 ThreadLocal。例如,在與資料庫有緊密聯絡的應用程式中,程式的很多方法可能都需要訪問資料庫。在系統的每個方法中都包含一個 Connection 作為引數是不方便的 — 用“單子”來訪問連線可能是一個雖然更粗糙,但卻方便得多的技術。然而,多個執行緒不能安全地共享一個 JDBC Connection。如清單 3 所示,通過使用“單子”中的 ThreadLocal,我們就能讓我們的程式中的任何類容易地獲取每執行緒 Connection 的一個引用。這樣,我們可以認為 ThreadLocal 允許我們建立每執行緒單子。
例:把一個 JDBC 連線儲存到一個每執行緒 Singleton 中
public class ConnectionDispenser {
private static class ThreadLocalConnection extends ThreadLocal {
public Object initialValue() {
return DriverManager.getConnection(ConfigurationSingleton.getDbUrl());
}
}
private ThreadLocalConnection conn = new ThreadLocalConnection();
public static Connection getConnection() {
return (Connection) conn.get();
}
}

注意:
理論上來說,ThreadLocal是的確是相對於每個執行緒,每個執行緒會有自己的ThreadLocal。但是上面已經講到,一般的應用伺服器都會維護一套執行緒池。因此,不同使用者訪問,可能會接受到同樣的執行緒。因此,在做基於TheadLocal時,需要謹慎,避免出現ThreadLocal變數的快取,導致其他執行緒訪問到本執行緒變數。

Servlet3中的AsyncContext非同步和多執行緒非同步有什麼區別
public class ListServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.getWriter().println(“I am begin output !”);
response.getWriter().flush();

    //方式一
    AsyncContext async = request.startAsync();
    new AsyncOutput(async).start();

    //方式二
    new ThreadOutput(response).start();

    response.getWriter().println("I am finash output !");
    response.getWriter().flush();
}

}

class AsyncOutput extends Thread
{
private AsyncContext async;
public AsyncOutput(AsyncContext async)
{
this.async = async;
}
public void run()
{
try
{
Thread.sleep(3000);
async.getResponse().getWriter().println(“I was three minutes late !”);
async.getResponse().getWriter().flush();
}catch(Exception e)
{
e.printStackTrace();
}
}
}

class ThreadOutput extends Thread
{
private HttpServletResponse response;
public ThreadOutput(HttpServletResponse response)
{
this.response = response;
}
public void run()
{
try
{
Thread.sleep(3000);
response.getWriter().println(“I was three minutes late !”);
response.getWriter().flush();
}catch(Exception e)
{
e.printStackTrace();
}
}
}
AsyncContext不是讓你非同步輸出,而是讓你同步輸出,但是解放伺服器端的執行緒使用,使用AsyncContext的時候,對於瀏覽器來說,他們是同步在等待輸出的,但是對於伺服器端來說,處理此請求的執行緒並沒有卡在那裡等待,則是把當前的處理轉為執行緒池處理了,關鍵就在於執行緒池,伺服器端會起一個執行緒池去服務那些需要非同步處理的請求,而如果你自己每次請求去起一個執行緒處理的話,這就有可能會耗大量的執行緒。

你目前對AsyncContext 的使用並不是最佳實踐,實際上應該這樣使用:

Java程式碼 收藏程式碼
final AsyncContext asyncContext = request.getAsyncContext();
asyncContext.addListener(new AsyncListener() {
@Override
public void onComplete(AsyncEvent event) throws IOException {
//在這裡處理正常結束的邏輯
}

            @Override  
            public void onTimeout(AsyncEvent event) throws IOException {  
                //在這裡處理超時的邏輯  
            }  

            @Override  
            public void onError(AsyncEvent event) throws IOException {  
                //在這裡處理出錯的邏輯  
            }  

            @Override  
            public void onStartAsync(AsyncEvent event) throws IOException {  
                //在這裡處理開始非同步執行緒的邏輯  
            }  
        });  
        //設定超時的時間,到了時間以後,會回撥onTimeout的方法  
        asyncContext.setTimeout(10000L);  
        //在這裡啟動,傳入一個Runnable物件,伺服器會把此Runnable物件放線上程池裡面執行  
        asyncContext.start(new Runnable() {  
            @Override  
            public void run() {  
                //在這裡做耗時的操作,如果做完,則呼叫complete方法通知回撥,非同步處理結束了  
                asyncContext.complete();  
            }  
        });  

使用非同步 Servlet 處理掛起執行緒
作者:Francesco Marchioni
12/04/2007

摘要
BEA WebLogic Server 9.2及以上版本將公開一個Abstract Asynchronous Servlet類,可用於解除接收servlet請求與傳送其響應之間的耦合。該類還提供了一個Future Response Servlet,用於支援伺服器使用一個不同的執行緒(而不是處理傳入請求的執行緒)來處理servlet響應。傳統servlet模型的這兩個擴充套件都可以避免掛起執行緒並將長時間執行的作業與servlet範例整合在一起。本文將介紹這兩個特性,同時將提供一些示例。

簡介
是否希望通過在Web應用程式中控制響應時間來實現服務質量(QoS)?從最簡單的形式來說,服務質量需求就是將響應時間控制在特定的時間(秒)內。如果無法滿足這一需求,則會提供一條有意義的錯誤訊息。

傳統的servlet執行緒模式相當簡單:應用伺服器分配特定數量的執行緒,並提供給Web應用程式使用(參見圖1)。當新請求傳入並可供服務使用時,應用程式將分配一個執行緒。從此之後,servlet執行緒將一直佔用記憶體池,直到它完成所有任務。

Traditional servlet threading model

圖1. 傳統servlet執行緒模式
這裡寫圖片描述

如果servlet需要執行一個長時間的任務,則會造成一些問題。 要解決這一問題,最常用的方法是使用JMS或Message Driven Bean解除Web請求與長時間執行後臺程序之間的耦合。這樣便可解決不需要將處理結果立即返回響應的情況。

這聽上去像是一個“Fire-and-forget”場景。但是,如果需要向客戶機返回一些資料,那麼servlet執行緒極有可能會成為掛起執行緒。

問題
在大多數情況下,掛起執行緒的意思就是在聯絡後臺系統獲取傳送請求所需要的資料時受到了阻塞。其典型場景就是通過JDBC連線遠端資料庫。

問題的關鍵在於,如果所需資源速度變慢或完全不可用,則需要確保應用程式知道如何處理這一情況。大多數情況下,這並不是什麼問題,因為超時套接字到遠端資料庫服務的程式碼都由資料來源和JDBC驅動程式處理。但是,在需要手動編寫超時策略時,該場景將變得極為複雜。

許多程式設計師都害怕處理網路超時。最常見的問題是,將沒有超時支援的單執行緒網路客戶機擴充套件為複雜的多執行緒時,每個單獨的執行緒都需要測試網路超時,並且阻塞執行緒與主應用程式之間需要某種形式的通知流程。

本文將介紹如何使用BEA WebLogic Server的未來響應模型(Future Response Model)來編寫能夠有效處理超時的Java Web應用程式。這一過程非常簡單。通過解除響應與傳入請求及超時無響應請求之間的耦合,該模型還可以防止掛起執行緒。為避免這種執行緒掛起場景,WebLogic Server提供了兩個類專門用於非同步處理HTTP請求,其原理是解除響應與處理傳入請求的執行緒之間的耦合。以下部分將詳細介紹這兩個類。

抽象非同步Servlet類
AbstractAsyncServlet類的實現將解除接收servlet請求與傳送響應之間的耦合。我們實現Abstract Asynchronous Servlet類的方法是擴充套件 weblogic.servlet.http.AbstractAsyncServlet 類。以下是需要實現的方法:

public boolean doRequest(RequestResponseKey rrk): 該方法是聯絡servlet的初始點。它接受的輸入引數為RequestResponseKey類,RequestResponseKey類是傳統servlet請求的包裝器。下文將會介紹,我們可以使用請求判斷servlet是否響應。
public void doResponse (RequestResponseKey rrk, Object context): 該方法將處理servlet響應。正如本文所述,初始執行緒並不需要處理此方法。
public void doTimeout (RequestResponseKey rrk): 如果未在特定時間段內傳送servlet響應,則伺服器將會觸發doTimeout()方法。
Servlet類還提供了一個靜態方法:

static void notify(RequestResponseKey rrk, Object context): 呼叫此方法將通知伺服器應該向鍵rrk傳送一個響應。
只實現抽象類還不足以實現解耦。解除請求與響應之間的耦合可以通過doRequest()方法中的 TimerListener 類來實現。可以使用工廠模式獲得TimerListener類的一個新例項(實際上是通過TimerManagerFactory)。TimerListener執行緒可以針對AbstractAsyncServlet例項呼叫notify()方法,從而最終觸發響應的傳送。以下示例將演示如何建立這種計時器:

TimerManagerFactory.getTimerManagerFactory().
getDefaultTimerManager().schedule(new TimerListener() {
public void timerExpired(weblogic.timers.Timer arg0) {
try { <