日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

j2ee servlet 和 threadlocal ,synchronized 与 web容器

發布時間:2024/4/14 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 j2ee servlet 和 threadlocal ,synchronized 与 web容器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
在傳統的Web開發中,我們處理Http請求最常用的方式是通過實現Servlet對象來進行Http請求的響應。Servlet是J2EE的重要標準之一,規定了Java如何響應Http請求的規范。通過HttpServletRequest和HttpServletResponse對象,我們能夠輕松地與Web容器交互。當Web容器收到一個Http請求時,Web容器中的一個主調度線程會從事先定義好的線程池中分配一個當前工作線程,將請求分配給當前的工作線程,由該線程來執行對應的Servlet對象中的service方法。如果這個工作線程正在執行的時候,Web容器收到另外一個請求,主調度線程會同樣從線程池中選擇另一個工作線程來服務新的請求。Web容器本身并不關心這個新的請求是否訪問的是同一個Servlet實例。因此,我們可以得出一個結論:對于同一個Servlet對象的多個請求,Servlet的service方法將在一個多線程的環境中并發執行。所以,Web容器默認采用單實例(單Servlet實例)多線程的方式來處理Http請求。這種處理方式能夠減少新建Servlet實例的開銷,從而縮短了對Http請求的響應時間。但是,這樣的處理方式會導致變量訪問的線程安全問題。也就是說,Servlet對象并不是一個線程安全的對象。下面的測試代碼將證實這一點:public class ThreadSafeTestServlet extends HttpServlet { // 定義一個實例變量,并非一個線程安全的變量 private int counter = 0; public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 輸出當前Servlet的信息以及當前線程的信息System.out.println(this + ":" + Thread.currentThread()); // 循環,并增加實例變量counter的值for (int i = 0; i < 5; i++) { System.out.println("Counter = " + counter); try { Thread.sleep((long) Math.random() * 1000); counter++; } catch (InterruptedException exc) { } } } }
這里參閱了網絡上一段著名的對Servlet線程安全性進行測試的代碼(http://zwchen.iteye.com/blog/91088)。運行之后,我們可以看一下這個例子的輸出:sample.SimpleServlet@11e1bbf:Thread[http-8081-Processor23,5,main]Counter = 60 Counter = 61 Counter = 62 Counter = 65 Counter = 68 Counter = 71 Counter = 74 Counter = 77 Counter = 80 Counter = 83 sample.SimpleServlet@11e1bbf:Thread[http-8081-Processor22,5,main] Counter = 61 Counter = 63 Counter = 66 Counter = 69 Counter= 72 Counter = 75 Counter = 78 Counter = 81 Counter = 84 Counter = 87 sample.SimpleServlet@11e1bbf:Thread[http-8081-Processor24,5,main] Counter = 61 Counter = 64 Counter = 67 Counter = 70 Counter = 73 Counter = 76 Counter = 79 Counter = 82 Counter = 85 Counter= 88通過上面的輸出,我們可以得出以下三個Servlet對象的運行特性:1. Servlet對象是一個無狀態的單例對象(Singleton),因為我們看到多次請求的this指針所打印出來的hashcode值都相同2. Servlet在不同的線程(線程池)中運行,如http-8081-Processor22和http-8081-Processor23等輸出值可以明顯區分出不同的線程執行了同一段Servlet邏輯代碼。3. Counter變量在不同的線程中共享,而且它的值被不同的線程修改,輸出時已經不是順序輸出。也就是說,其他的線程會篡改當前線程中實例變量的值,針對這些對象的訪問不是線程安全的。【有關線程安全的概念范疇】談到線程安全,對于許多初學者來說很容易引起概念上的混淆。線程安全,指的是在多線程環境下,一個類在執行某個方法時,對類的內部實例變量的訪問安全與否。因此,對于下面列出來的2類變量,不存在任何線程安全的說法:1)方法簽名中的任何參數變量。2)處于方法內部的局部變量。任何針對上述形式的變量的訪問都是線程安全的,因為它們都處于方法體的內部,由當前的執行線程獨自管理。這就是線程安全問題的由來:在傳統的基于Servlet的開發模式中,Servlet對象內部的實例變量不是線程安全的。在多線程環境中,這些變量的訪問需要通過特殊的手段進行訪問控制。解決線程安全訪問的方法很多,比較容易想到的一種方案是使用同步機制,但是出于對Web應用效率的考慮,這種機制在Web開發中的可行性很低,也違背了Servlet的設計初衷。因此,我們需要另辟蹊徑來解決這一困擾我們的問題。4.1.2ThreadLocal模式的實現機理在JDK的早期版本中,提供了一種解決多線程并發問題的方案: java.lang.ThreadLocal類。ThreadLocal類在維護變量時,實際使用了當前線程(Thread)中的一個叫做ThreadLocalMap的獨立副本,每個線程可以獨立修改屬于自己的副本而不會互相影響,從而隔離了線程和線程,避免了線程訪問實例變量發生沖突的問題。ThreadLocal本身并不是一個線程,而是通過操作當前線程(Thread)中的一個內部變量來達到與其他線程隔離的目的。之所以取名為ThreadLocal,所期望表達的含義是其操作的對象是線程(Thread)的一個本地變量。如果我們看一下Thread的源碼實現,就會發現這一變量,如代碼清單4-2所示:publicclass Thread implements Runnable { // 這里省略了許多其他的代碼ThreadLocal.ThreadLocalMap threadLocals = null;}這是JDK中Thread源碼的一部分,從中我們可以看出ThreadLocalMap跟隨著當前的線程而存在。不同的線程Thread,擁有不同的ThreadLocalMap的本地實例變量,這也就是“副本”的含義。接下來我們再來看看ThreadLocal.ThreadLocalMap是如何定義的,以及ThreadLocal如何來操作它,如代碼清單4-3所示:public class ThreadLocal<T> {// 這里省略了許多其他代碼// 將value的值保存于當前線程的本地變量中 public void set(T value) {// 獲取當前線程Thread t = Thread.currentThread();// 調用getMap方法獲得當前線程中的本地變量ThreadLocalMapThreadLocalMap map = getMap(t);// 如果ThreadLocalMap已存在,直接使用if (map != null)// 以當前的ThreadLocal的實例作為key,存儲于當前線程的// ThreadLocalMap中,如果當前線程中被定義了多個不同的ThreadLocal// 的實例,則它們會作為不同key進行存儲而不會互相干擾map.set(this, value);else// ThreadLocalMap不存在,則為當前線程創建一個新的createMap(t, value); }// 獲取當前線程中以當前ThreadLocal實例為key的變量值 public T get() {// 獲取當前線程Thread t = Thread.currentThread();// 獲取當前線程中的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {// 獲取當前線程中以當前ThreadLocal實例為key的變量值ThreadLocalMap.Entry e = map.getEntry(this);if (e != null)return (T)e.value;}// 當map不存在時,設置初始值return setInitialValue(); }// 從當前線程中獲取與之對應的ThreadLocalMap ThreadLocalMap getMap(Thread t) {return t.threadLocals; }// 創建當前線程中的ThreadLocalMap void createMap(Thread t, T firstValue) {// 調用構造函數生成當前線程中的ThreadLocalMapt.threadLocals = new ThreadLocalMap(this, firstValue); }// ThreadLoaclMap的定義 static class ThreadLocalMap {// 這里省略了許多代碼 } }
從上述代碼中,我們看到了ThreadLocal類的大致結構和進行ThreadLocalMap的操作。我們可以從中得出以下的結論:1. ThreadLocalMap變量屬于線程(Thread)的內部屬性,不同的線程(Thread)擁有完全不同的ThreadLocalMap變量。2. 線程(Thread)中的ThreadLocalMap變量的值是在ThreadLocal對象進行set或者get操作時創建的。3. 在創建ThreadLocalMap之前,會首先檢查當前線程(Thread)中的ThreadLocalMap變量是否已經存在,如果不存在則創建一個;如果已經存在,則使用當前線程(Thread)已創建的ThreadLocalMap。4.使用當前線程(Thread)的ThreadLocalMap的關鍵在于使用當前的ThreadLocal的實例作為key進行存儲。ThreadLocal模式,至少從兩個方面完成了數據訪問隔離,有了橫向和縱向的兩種不同的隔離方式,ThreadLocal模式就能真正地做到線程安全:縱向隔離 —— 線程(Thread)與線程(Thread)之間的數據訪問隔離。這一點由線程(Thread)的數據結構保證。因為每個線程(Thread)在進行對象訪問時,訪問的都是各自線程自己的ThreadLocalMap。橫向隔離 ——同一個線程中,不同的ThreadLocal實例操作的對象之間的相互隔離。這一點由ThreadLocalMap在存儲時,采用當前ThreadLocal的實例作為key來保證。ThreadLocal模式并不是什么高深的學問,它甚至從JDK1.2開始就存在于Java世界中。由此可見,我們掌握一種知識的最終目的是熟練而合理地運用它。【深入比較ThreadLocal模式與synchronized關鍵字】ThreadLocal模式與synchronized關鍵字都是用于處理多線程并發訪問變量的問題。只是兩者處理問題的角度和思路不同。1)ThreadLocal是一個Java類,通過對當前線程(Thread)中的局部變量的操作來解決不同線程的變量訪問的沖突問題。所以,ThreadLocal提供了線程安全的共享對象機制,每個線程(Thread)都擁有其副本。2)Java中的synchronized是一個保留字,它依靠JVM的鎖機制來實現臨界區的函數或者變量在訪問中的原子性。在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。此時,被用作“鎖機制”的變量是多個線程共享的。同步機制采用了“以時間換空間”的方式,提供一份變量,讓不同的線程排隊訪問。而ThreadLocal采用了“以空間換時間”的方式,為每一個線程都提供了一份變量的副本,從而實現同時訪問而互不影響。4.1.3ThreadLocal模式的應用場景在分析了ThreadLocal的源碼之后,我們來看看ThreadLocal模式最合適的業務場景。在一個完整的“請求-響應”過程中,主線程的執行過程總是貫穿始終。當這個主線程的執行過程中被加入了ThreadLocal的讀寫時,會對整個過程產生怎樣的影響呢?我們根據之前源碼分析的結果,并結合分層開發模式,把整個流程畫下來,如圖4-1所示:從上面圖中我們可以看到,由于ThreadLocal所操作的是維持于整個Thread生命周期的副本(ThreadLocalMap),所以無論在J2EE程序程序的哪個層次(表示層、業務邏輯層或者持久層),只要在一個Thread的生命周期之內,存儲于ThreadLocalMap中的對象都是線程安全的(因為ThreadLocalMap本身僅僅隸屬于當前的執行線程,是執行線程內部的一個屬性變量。我們用圖中的陰影部分來表示這個變量的存儲空間)。而這一點,正是被我們用于來解決多線程環境中的變量共享問題的核心技術。ThreadLocal的這一特性也使其能夠被廣泛地應用于J2EE開發中的許多業務場景。【數據共享OR 數據傳遞?】ThreadLocal模式由于利用了Java自身的語法特性而顯得異常簡單和便利,因而被廣泛應用于J2EE開發,尤其是應對跨層次的資源共享,例如在Spring中,就有使用ThreadLocal模式來管理數據庫連接或者Hibernate的Session的范例。在一些比較著名的論壇中,有著很多關于使用ThreadLocal模式來做數據傳遞的討論。事實上,這是對ThreadLocal模式的一個極大的誤解。讀者需要注意的是,ThreadLocal模式解決的是同一線程中隸屬于不同開發層次的數據共享問題,而不是在不同的開發層次中進行數據傳遞。1)ThreadLocal模式的核心在于實現一個共享環境(類的內部封裝了ThreadLocal的靜態實例)。所以,在操作ThreadLocal時,這一共享環境會跨越多個開發層次而隨處存在。2)隨處存在的共享環境造成了所有的開發層次的共同依賴,從而使得所有的開發層次都耦合在了一起,從而變得無法獨立測試。3)數據傳遞應該通過接口函數的簽名顯式聲明,這樣才能夠從接口聲明中表達接口所表達的真正含義。ThreadLocal模式位于實現的內部,從而使得接口與接口之間無法達成一致的聲明契約。Struts2的解耦合的設計理念使得Struts2的MVC實現成為了使用ThreadLocal模式的天然場所。在第三章中,我們已經介紹了一些基本概念,Struts2通過引入XWork框架,將整個Http請求的過程拆分成為與Web容器有關和與Web容器無關的兩個執行階段。而這兩個階段的數據交互就是通過ThreadLocal模式中的線程共享副本安全地進行。在其中,我們沒有看到數據傳遞,存在的只是整個執行線程的數據共享。4.1.4ThreadLocal模式的核心元素仔細分析上一節的示意圖(圖4-1),我們可以發現,要完成ThreadLocal模式,其中最關鍵的地方就是創建一個任何地方都可以訪問到的ThreadLocal實例(也就是執行示意圖中的菱形部分)。而這一點,我們可以通過類的靜態實例變量來實現,這個用于承載靜態實例變量的類就被視作是一個共享環境。我們來看一個例子,如代碼清單4-4所示:public class Counter {// 新建一個靜態的ThreadLocal變量,并通過get方法將其變為一個可訪問的對象 privatestatic ThreadLocal<Integer> counterContext = new ThreadLocal<Integer>() { protected synchronized Integer initialValue() { return 10; }};// 通過靜態的get方法訪問ThreadLocal中存儲的值public static Integer get() {return counterContext.get();}// 通過靜態的set方法將變量值設置到ThreadLocal中publicstatic void set(Integer value) {counterContext.set(value);}// 封裝業務邏輯,操作存儲于ThreadLocal中的變量public static Integer getNextCounter() { counterContext.set(counterContext.get() + 1); return counterContext.get();} }在這個Counter類中,我們實現了一個靜態的ThreadLocal變量,并通過get方法將ThreadLocal中存儲的值暴露出來。我們還封裝了一個帶有業務邏輯的方法getNextCounter,操作ThreadLocal中的值,將其加1,并返回計算后的值。此時,Counter類就變成了一個數據共享環境,我們也擁有了實現ThreadLocal模式的關鍵要素。有了它,我們來編寫一個簡單的測試,如代碼清單4-5所示:publicclass ThreadLocalTest extends Thread { public void run() {for(int i = 0; i < 3; i++){ System.out.println("Thread[" + Thread.currentThread().getName() + "],counter=" + Counter.getNextCounter());}}}這是一個簡單的線程類,循環輸出當前線程的名稱和getNextCounter的結果,由于getNextCounter中的邏輯所操作的是ThreadLocal中的變量,所以無論同時有多少個線程在運行,返回的值將僅與當前線程的變量值有關,也就是說,在同一個線程中,變量值會被連續累加。這一點可以通過如下的測試代碼證實:publicclass Test {public static void main(String[] args) throws Exception {ThreadLocalTest testThread1 = new ThreadLocalTest();ThreadLocalTest testThread2 = new ThreadLocalTest();ThreadLocalTest testThread3 = new ThreadLocalTest();testThread1.start();testThread2.start();testThread3.start();}}我們來運行一下上面的代碼,并看看輸出結果:Thread[Thread-2],counter=11Thread[Thread-2],counter=12Thread[Thread-2],counter=13Thread[Thread-0],counter=11Thread[Thread-0],counter=12Thread[Thread-0],counter=13Thread[Thread-1],counter=11Thread[Thread-1],counter=12Thread[Thread-1],counter=13上面的輸出結果也證實了,counter的值在多線程環境中的訪問是線程安全的。從對例子的分析中我們可以再次體會到,ThreadLocal模式最合適的使用場景:在同一個線程(Thread)的不同開發層次中共享數據。從上面的例子中,我們可以簡單總結出實現ThreadLocal模式的兩個主要步驟:1.建立一個類,并在其中封裝一個靜態的ThreadLocal變量,使其成為一個共享數據環境。2. 在類中實現訪問靜態ThreadLocal變量的靜態方法(設值和取值)。建立在ThreadLocal模式的實現步驟之上,ThreadLocal的使用則更加簡單。在線程執行的任何地方,我們都可以通過訪問共享數據類中所提供的ThreadLocal變量的設值和取值方法安全地獲得當前線程中安全的變量值。這兩個步驟,我們之后會在Struts2的實現中多次提及,讀者只要能充分理解ThreadLocal處理多線程訪問的基本原理,就能對Struts2的數據訪問和數據共享的設計有一個整體的認識。講到這里,我們回過頭來看看ThreadLocal模式的引入,到底對我們的編程模型有什么重要的意義呢?downpour寫道結論 使用ThreadLocal模式,可以使得數據在不同的編程層次得到有效地共享。這一點,是由ThreadLocal模式的實現機理決定的。因為實現ThreadLocal模式的一個重要步驟,就是構建一個靜態的共享存儲空間。從而使得任何對象在任何時刻都可以安全地對數據進行訪問。downpour 寫道結論 使用ThreadLocal模式,可以對執行邏輯與執行數據進行有效解耦。這一點是ThreadLocal模式給我們帶來的最為核心的一個影響。因為在一般情況下,Java對象之間的協作關系,主要通過參數和返回值來進行消息傳遞,這也是對象協作之間的一個重要依賴。而ThreadLocal模式徹底打破了這種依賴關系,通過線程安全的共享對象來進行數據共享,可以有效避免在編程層次之間形成數據依賴。這也成為了XWork事件處理體系設計的核心。

總結

以上是生活随笔為你收集整理的j2ee servlet 和 threadlocal ,synchronized 与 web容器的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。