|
1 2 3 4 5 6 7 8 9 10 11 12 | public class MyServlet extends HttpServlet {
final static int i = 0;
public void doGet(HttpServletRequest req, HttpServletResponse res) {
private HttpSession session = req.getSession();
private ServletContext ctx = getServletContext();
synchronized (ctx) {
Object obj = ctx.getAttribute();
// code to alter obj
}
}
}
|
上面代碼中的哪些變量是線程安全的?
選擇:
* A. i
* B. session
* C. ctx
* D. req
* E. obj
* F. res
IBM 給出的答案是:
正確答案:
* A、C、D 和 F
說明:
靜態(tài)變量 i 是線程安全的,因?yàn)樗?final(不能被修改),否則它將不是安全的。請求和響應(yīng)對象的作用域只在請求的生命周期,因此它們也是線程安全的。會話和 ServletContext 對象可以從多個線程訪問,同時處理多個請求,因此它們不是線程安全的。但在本例中,同步了 ServletContext 對象,因此它只能由一個線程一次訪問。obj 不是線程安全的,因?yàn)榧词雇搅?ServletContext 對象,它的屬性也沒有同步。它們需要另外進(jìn)行同步。因此,選項(xiàng) B 和 E 是不正確的,而選項(xiàng) A、C、D 和 F 是正確的。
Servlets的多線程安全
多線程占用資源少,處理速度快,提高了效率。
一些編碼建議:
對變量和方法定義適當(dāng)?shù)脑L問方式, 例如單純?nèi)≈挡僮鞑粫卸嗑€程安全問題;
同步化所有訪問重要數(shù)據(jù)的實(shí)例變量; 多線程下,如果操作的是一個變量,且兼有讀寫操作,
就要考慮加上同步,但同步不能亂加,否則會造成死鎖問題。
并發(fā)需要注意的
并發(fā)的環(huán)境:資源處于一個并發(fā)的環(huán)境
共享資源:多個線程共享一個臨界資源
全面同步:如有n個變量訪問同一個資源,這n個變量都得同步。即多個鎖一把鑰匙,鑰匙放在一個共享區(qū)域內(nèi)
sychronized(this):粗粒度的鎖。是將所有的路都加鎖;
sychronized(object o1):細(xì)粒度的鎖。只對對象中的變量加鎖。效率較前面的高,但是較難控制。
讀寫需要互斥。
sychronized(this):this不能是基本數(shù)據(jù)類型,必須是Object.不鎖對象的引用,而是對象的內(nèi)存空間。
servlet中需要同步的:成員變量、文件、靜態(tài)變量、數(shù)據(jù)庫連接
一,servlet容器如何同時處理多個請求。
Servlet采用多線程來處理多個請求同時訪問,Servlet容器維護(hù)了一個線程池來服務(wù)請求。
線程池實(shí)際上是等待執(zhí)行處理的一組線程,也叫做工作者線程(Worker Thread),
Servlet容器使用一個調(diào)度線程來管理工作者線程(Dispatcher Thread)。
當(dāng)容器收到一個訪問Servlet的請求,調(diào)度者線程從線程池中選出一個工作者線程,將請求傳遞給該線程,
然后由該線程來執(zhí)行Servlet的service方法。
當(dāng)這個線程正在執(zhí)行的時候,容器收到另外一個請求,調(diào)度者線程將從池中選出另外一個工作者線程來服務(wù)新的請求;
容器并不關(guān)心這個請求是否訪問的是同一個Servlet還是另外一個Servlet;
當(dāng)容器同時收到對同一Servlet的多個請求,那這個Servlet的service方法將以多線程方式并發(fā)執(zhí)行。
二,Servlet容器默認(rèn)采用單實(shí)例多線程的方式來處理請求,減少產(chǎn)生Servlet實(shí)例的開銷,提升了對請求的響應(yīng)。
對于Tomcat可以在server.xml中通過元素設(shè)置線程池中線程的數(shù)目。
就實(shí)現(xiàn)來說:
調(diào)度者線程類所擔(dān)負(fù)的責(zé)任是調(diào)度線程,只需要利用自己的屬性完成自己的責(zé)任。
而其他對象又依賴于該對象所承擔(dān)的責(zé)任,需要得到該特定對象,那該類就是一個單例模式的實(shí)現(xiàn)了。
三,如何開發(fā)線程安全的Servlet
1,變量的線程安全:這里的變量指字段和共享數(shù)據(jù)(如表單參數(shù)值)。
a,將參數(shù)變量本地化:多線程并不共享局部變量.所以我們要盡可能的在servlet中使用局部變量。
例如:String user = request.getParameter(“user”);
b,使用同步塊Synchronized,防止可能異步調(diào)用的代碼塊。這意味著線程需要隊(duì)列處理。
在使用同板塊的時候要盡可能的縮小同步代碼的范圍,不要直接在sevice方法和響應(yīng)方法上使用同步,這樣會嚴(yán)重影響性能。
2,屬性的線程安全分析:ServletContext,HttpSession,ServletRequest對象的屬性
ServletContext:(線程是不安全的)
ServletContext是可以多線程同時讀/寫屬性的,線程是不安全的。要對屬性的讀寫進(jìn)行同步處理或者進(jìn)行深度Clone()。
所以在Servlet上下文中盡可能少地保存頻繁改寫的數(shù)據(jù),可以采取其他方式在多個Servlet中共享,比方我們可以使用單例模式來處理共享數(shù)據(jù)。
HttpSession:(線程是不安全的)
HttpSession對象在用戶會話期存在,只能處理屬于同一個Session的請求的線程,因此Session對象的屬性訪問理論上是線程安全的。
當(dāng)用戶打開多個同屬于一個進(jìn)程的瀏覽器窗口,在這些窗口的訪問屬于同一個Session,會出現(xiàn)多次請求,需要多個工作線程來處理請求,可能造成同時多線程讀寫屬性,這時我們對屬性的讀寫進(jìn)行同步處理。
ServletRequest:(線程是安全的)
對于每一個請求,由一個工作線程來執(zhí)行,都會創(chuàng)建有一個新的ServletRequest對象,所以ServletRequest對象只能在一個線程中被訪問。ServletRequest是線程安全的。
注意:ServletRequest對象在service方法的范圍內(nèi)是有效的,不要試圖在service方法結(jié)束后仍然保存請求對象的引用。
3,使用同步的集合類:
使用Vector代替ArrayList,使用Hashtable代替HashMap。
4,不要在Servlet中創(chuàng)建自己的線程來完成某個功能:
Servlet本身就是多線程的,在Servlet中再創(chuàng)建線程,將導(dǎo)致執(zhí)行情況復(fù)雜化,出現(xiàn)安全問題。
5,在多個servlet中,對外部對象(例如文件)進(jìn)行修改操作一定要加鎖,做到互斥的訪問
四,SingleThreadModel接口
javax.servlet.SingleThreadModel接口是一個標(biāo)識接口,如果一個Servlet實(shí)現(xiàn)了這個接口,
則Servlet容器將保證在同時刻僅有一個線程可以在該servlet實(shí)例的service方法中執(zhí)行,將其他所有請求進(jìn)行排隊(duì)。
服務(wù)器可以使用多個實(shí)例來處理請求,代替單個實(shí)例的請求排隊(duì)帶來的性能問題。
服務(wù)器可創(chuàng)建一個Servlet類的多個實(shí)例組成的實(shí)例池,對于每個請求分配Servlet實(shí)例進(jìn)行響應(yīng),之后放回到實(shí)例池中等待下此請求。此時,局部變量(字段)也是安全的,但對于全局變量和共享數(shù)據(jù)是不安全的,需要進(jìn)行同步處理。
而對于這種多實(shí)例的情況,使用SingleThreadModel接口并不能解決并發(fā)訪問產(chǎn)生的問題,
且SingleThreadModel接口在servlet規(guī)范中已經(jīng)被明確聲明為deprecated了。
|