天天看點

Servlet的多線程和線程安全

首先說明一下對線程安全的讨論,哪種情況我們可以稱作線程安全?

網上對線程安全有很多描述,我比較喜歡《java并發程式設計實戰》給出的定義,“當多個線程通路某個類時,不管運作時環境采用何種排程方式,或者這些線程将如何交替執行,并且在主調代碼中不需要任何額外的同步或協同,這個類都能表現出正确的行為,那麼就稱這個類是線程安全的”。

servlet是運作在servlet容器中的,常用的tomcat、jboss、weblogic都是servlet容器,其生命周期是由容器來管理。servlet的生命周期通過java.servlet.servlet接口中的init()、service()、和destroy()方法表示。servlet的生命周期有四個階段:加載并執行個體化、初始化、請求處理、銷毀。

加載并執行個體化

servlet容器負責加載和執行個體化servelt。當servlet容器啟動時,或者在容器檢測到需要這個servlet來響應第一個請求時,建立servlet執行個體。當servlet容器啟動後,servlet通過類加載器來加載servlet類,加載完成後再new一個servlet對象來完成執行個體化。

初始化

在servlet執行個體化之後,容器将調用init()方法,并傳遞實作servletconfig接口的對象。在init()方法中,servlet可以部署描述符中讀取配置參數,或者執行任何其他一次性活動。在servlet的整個生命周期類,init()方法隻被調用一次。

請求處理

當servlet初始化後,容器就可以準備處理客戶機請求了。當容器收到對這一servlet的請求,就調用servlet的service()方法,并把請求和響應對象作為參數傳遞。當并行的請求到來時,多個service()方法能夠同時運作在獨立的線程中。通過分析servletrequest或者httpservletrequest對象,service()方法處理使用者的請求,并調用servletresponse或者httpservletresponse對象來響應。

銷毀

一旦servlet容器檢測到一個servlet要被解除安裝,這可能是因為要回收資源或者因為它正在被關閉,容器會在所有servlet的service()線程之後,調用servlet的destroy()方法。然後,servlet就可以進行無用存儲單元收集清理。這樣servlet對象就被銷毀了。這四個階段共同決定了servlet的生命周期。

1.用戶端通過發送請求給tomcat,tomcat發送用戶端的請求頁面給用戶端。

2.使用者對請求頁面進行相關操作後将頁面送出給tomcat,tomcat将其封裝成一個httprequest對象,然後對請求進行處理,。

3.tomcat截獲請求,根據action屬性值查詢xml檔案中對應的servlet-name,再根據servlet-name查詢到對應的java類(如果是第一次,tomcat則會将servlet編譯成java類檔案,是以如果servlet有很多的話第一次運作的時候程式會比較慢)。

4.tomcat執行個體化查詢到的java類,注意該類隻執行個體化一次。

5.調用java類對象的service()方法(如果不對service()方法進行重寫則根據送出的方式來決定執行dopost()方法還是doget()方法)。

6.通過request對象取得用戶端傳過來的資料,對資料進行處理後通過response對象将處理結果寫回用戶端。

從上面servlet的調用過程可以看出,當用戶端第一次請求servlet的時候,tomcat會根據web.xml配置檔案執行個體化servlet,

當又有一個用戶端通路該servlet的時候,不會再執行個體化該servlet,也就是多個線程在使用這個執行個體。

jsp/servlet容器預設是采用單執行個體多線程(這是造成線程安全的主因)方式處理多個請求的,這種預設以多線程方式執行的設計可大大降低對系統的資源需求,提高系統的并發量及響應時間。

servlet本身是無狀态的,一個無狀态的servlet是絕對線程安全的,無狀态對象設計也是解決線程安全問題的一種有效手段。

是以,servlet是否線程安全是由它的實作來決定的,如果它内部的屬性或方法會被多個線程改變,它就是線程不安全的,反之,就是線程安全的。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<code>public</code> <code>class</code> <code>unsafecountingfactorizer </code><code>implements</code> <code>servlet{</code>

<code>private</code> <code>long</code> <code>count=</code><code>0</code><code>;</code>

<code>public</code> <code>long</code> <code>getcount(){</code>

<code>return</code> <code>count;</code>

<code>}</code>

<code>@override</code>

<code>public</code> <code>void</code> <code>service(servletrequest req, servletresponse resp)</code>

<code>throws</code> <code>servletexception, ioexception {</code>

<code>biginteger i=extractfromrequest();</code>

<code>biginteger[] factors=factor(i);</code>

<code>++count;</code>

遞增操作count++并非是原子操作,它包含了三個獨立的操作:讀取count的值,将值加1,

然後将計算結果寫入coune,這是一個“讀取-修改-寫入”的操作序列,并且其結果狀态依賴于之前的狀态。在執行時序不同的情況下,可能會産生錯誤。

多線程下每個線程對局部變量都會有自己的一份copy,這樣對局部變量的修改隻會影響到自己的copy而不會對别的線程産生影響,是以這是線程安全的。

但是對于執行個體變量來說,由于servlet在tomcat中是以單例模式存在的,所有的線程共享執行個體變量。多個線程對共享資源的通路就造成了線程不安全問題。

避免使用執行個體變量

避免使用非線程安全的集合

在多個servlet中對某個外部對象(例如檔案)的修改是務必加鎖(synchronized,或者reentrantlock),互斥通路。

屬性的線程安全:servletcontext、httpsession是線程安全的;servletrequest是非線程安全的。

1.實作 singlethreadmodel 接口

該接口指定了系統如何處理對同一個servlet的調用。如果一個servlet被這個接口指定,那麼在這個servlet中的service方法将不會有兩個線程被同時執行,當然也就不存線上程安全的問題。但是,如果一個servlet實作了singlethreadmodel接口,servlet引擎将為每個新的請求建立一個單獨的servlet執行個體,這将引起大量的系統開銷,在現在的servlet開發中基本看不到singlethreadmodel的使用,這種方式了解即可,盡量避免使用。

<code>public</code> <code>class</code> <code>xxxxx </code><code>extends</code> <code>httpservlet </code><code>implements</code> <code>singlethreadmodel {</code>

<code>…………</code>

2.同步對共享資料的操作

使用synchronized 關鍵字能保證一次隻有一個線程可以通路被保護的區段,可以通過同步塊操作來保證servlet的線程安全。如果在程式中使用同步來保護要使用的共享的資料,也會使系統的性能大大下降。這是因為被同步的代碼塊在同一時刻隻能有一個線程執行它,使得其同時處理客戶請求的吞吐量降低,而且很多客戶處于阻塞狀态。另外為保證主存内容和線程的工作記憶體中的資料的一緻性,要頻繁地重新整理緩存,這也會大大地影響系統的性能。是以在實際的開發中也應避免或最小化servlet 中的同步代碼。

同步代碼:

<code>public </code><code>class</code> <code>xxxxxx </code><code>extends</code> <code>httpservlet {</code>

<code>synchronized</code> <code>(</code><code>this</code><code>){xxxx}</code>

3.避免使用執行個體變量

線程安全問題很大部分是由執行個體變量造成的,隻要在servlet裡面的任何方法裡面都不使用執行個體變量,那麼該servlet就是線程安全的。

在servlet中避免使用執行個體變量是保證servlet線程安全的最佳選擇。

java 記憶體模型中,方法中的臨時變量是在棧上配置設定空間,而且每個線程都有自己私有的棧空間,是以它們不會影響線程的安全。