前言:前面說了很多關(guān)于Servlet的一些基礎(chǔ)知識,這一篇主要說一下關(guān)于Servlet的線程安全問題。 1:多線程的Servlet模型要想弄清Servlet線程安全我們必須先要明白Servlet實例是如何創(chuàng)建,它的模式是什么樣的。 在默認(rèn)的情況下Servlet容器對聲明的Servlet,只創(chuàng)建一個Servlet實例,那么如果要是多個客戶同時請求訪問這個Servlet,Servlet容器就采取多線程。下面我們來看一幅圖 從圖中可以看出當(dāng)客戶發(fā)送請求的時候,Servlet容器通過調(diào)度者線程從線程池中選擇一個線程,然后將請求傳遞給這個線程,然后在由這個線程去執(zhí)行Servlet的Service方法。 如果多個客戶端同時請求執(zhí)行一個Servlet實例,那么這個Servlet容器的Service方法將在多個線程中并發(fā)執(zhí)行(比喻圖中客戶1,客戶2,客戶3同時調(diào)用Servlet1實例,那么調(diào)度者線程就會在線程池中調(diào)用3個線程分別用于客戶1,2,3的請求,然后3個線程同時并發(fā)執(zhí)行Servlet1實例的Service方法)因為Servlet容器采取的單實例多線程的方法,那么就大大的減小了Servlet實例創(chuàng)建的開銷,提升了對請求的響應(yīng)時間,也是這樣引起了Servlet線程安全問題。所以我們下面說線程安全問題。 2:Servlet的線程安全2.1:變量的線程安全2.1.1:變量為啥會存在線程安全我們先看一段代碼 1 public class HelloWorldServlet extends HttpServlet{ 2 private String userName; 3 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException 4 { 5 userName=request.getParameter("userName"); 6 PrintWriter out=response.getWriter(); 7 if(userName!=null&&userName!="") 8 { 9 out.print(userName);10 }11 else {12 out.println("用戶名不存在");13 }14 }15 } 我們來分析這段代碼,現(xiàn)在有A,B2個客戶端同時請求HelloWorldServlet 這個實例,Servlet容器分配線程T1來服務(wù)A客戶端的請求,T2來服務(wù)B客戶端的請求,操作系統(tǒng)首先調(diào)用T1來運行,T1運行到第6行的時候得到了用戶名為張三并保存,此時時間片段到了,操作系統(tǒng)開始調(diào)用T2運行也運行到第6行但是這個用戶名是李四,此時時間片段又到了,操作系統(tǒng)又開始運行T1,從第7行開始運行,但是此時的用戶名卻成了李四,輸出的時候確實李四(很明顯是錯誤的),那么這個時候就出現(xiàn)了線程安全問題。 2.1.2:如何防止變量的線程安全
protected synchronized void doGet(HttpServletRequest request, HttpServletResponse response) 采用這種方式明顯不合適,因為這樣T2必須要等T1執(zhí)行完畢以后才可以執(zhí)行,大大的影響了效率。 3.如果是靜態(tài)資源則加上final表示這個資源不可以改變 比喻 final static String url="jdbc:mysql://localhost:3306/blog"; 2.2:屬性的線程安全在Servlet中可以訪問保存在ServletContext,HttpSession,ServletRequest對象中的屬性,這三種對象都提供了getAttribute(),setAttribute() 方法用來對取和設(shè)置屬性,那么這三個不同范圍對象的屬性訪問是否線程安全呢,下面我們來一起看一下 2.2.1:ServletContext首先明確一點是ServletContext是被應(yīng)用程序下所有的Servlet所共享的,那么ServletContext對象就可以被web應(yīng)用程序所有的Servlet訪問,那么這樣一來多個Servlet就可以同時對ServletContext的屬性進行設(shè)置和訪問,所以這個時候就會出現(xiàn)線程安全問題。我們來看一段代碼 1 protected void service(HttpServletRequest request, HttpServletResponse response) 2 { 3 String userName=request.getParameter("userName"); 4 if ("login") { 5 List list=(List)getServletContext().getAttribute("userList"); 6 list.add(userName); 7 } 8 else { 9 List list=(List)getServletContext().getAttribute("userList");10 list.remove(userName);11 }12 } 1 protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException 2 { 3 List list=(List)getServletContext().getAttribute("userList"); 4 int count=list.size(); 5 for(int i=0;i<count;i++) 6 { 7 PrintWriter out=response.getWriter(); 8 out.println(list.get(i)); 9 }10 } 第一段代碼是當(dāng)用戶登錄以后把用戶名保存在ServletContext屬性中,如果不是登錄就刪除這個用戶 第二段代碼就是查看應(yīng)用程序所有的用戶登錄情況,那么我們看如何出現(xiàn)線程安全問題的 當(dāng)2個請求并發(fā)執(zhí)行的時候,可能第二段代碼剛剛執(zhí)行第五行的時候獲取的count=5;但是呢另一個請求恰好執(zhí)行第一段代碼第十行,把其中的某個用戶刪除了,當(dāng)?shù)诙未a在循環(huán)遍歷的時候運行到count=5的時候就會數(shù)組超過索性界限異常。那么此時就出現(xiàn)了線程安全問題。那么遇到這樣的問題怎么解決呢,第一就是把ServletContext屬性值進行拷貝保存起來,第二就是采用synchronized 進行同步(這個效率低) 2.2.2:HttpSessionhttpSession對象在用戶會話期間存活的,不像ServletContext一樣被所有的用戶共享,所以說一個HttpSession在同一個時刻只用一個用戶進行請求的,因此理論看來Session是線程安全的,其實并不是如此,這個和瀏覽器有關(guān),在上一篇Session我們說過,同一個瀏覽器只能具有一個Session,那么這樣一來就會出現(xiàn)Session線程安全問題,看如下代碼 1 protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException 2 { 3 String commandType=request.getParameter("commandType"); 4 HttpSession session=request.getSession(); 5 List list=(List)session.getAttribute("items"); 6 if ("add".equals(commandType)) { 7 //添加 8 } 9 else if("delete".equals(commandType)){10 //刪除11 }12 else {13 int count=list.size();14 for (int i = 0; i < count; i++) {15 //遍歷16 }17 }18 } 上面是一個添加物品信息的一個簡單偽代碼,如果用戶現(xiàn)在在一個瀏覽器窗口刪除一件物品的同時又在另一個窗口去獲取所有的物品這個時候就會出現(xiàn)線程安全,從上面的介紹得知Servlet容器是多線程單實例的,這個時候Servlet容器就會分配2個線程來分別為刪除物品和獲取所有物品進行服務(wù),如果其中一個線程剛好運行到14行時間片段結(jié)束,另一個線程這個時候又運行第10行刪除一條物品信息,然后第一個線程又開始運行第15開始遍歷,此時同樣出現(xiàn)了上面數(shù)組索性超出范圍的錯誤。 2.2.3:HttpRequesthttprequest是線程安全的,因為每個請求都會調(diào)用Service,都會創(chuàng)建一個新的HttpRequest和局部變量一樣。 3:SingleThreadModel從名字很好理解,就是單線程模式,也就是說如果Servlet實現(xiàn)了SingleThreadModel接口,Servlet容器就保證一個時刻只有一個線程在Servlet實例的Service方法運行(其實和同步差不多)這樣一來就很影響效率了,現(xiàn)在SingleThreadModel已經(jīng)被廢棄了,值得注意的是就算Servlet實現(xiàn)了SingleThreadModel接口并不一定保證線程安全,比喻上面說的ServletContext,HttpSession,因為ServletContext是應(yīng)用程序共享的,可能2個Servlet實例同時運行造成線程安全,HttpSession因為是在同一瀏覽器共享的所以也會出現(xiàn)(雖然可能性很?。?/p> 4:總結(jié)1:只要我們了解Servlet容器工作的模式,可能就能夠理解為什么Servlet會出現(xiàn)線程安全問題,所以一定牢記Servlet容器是多線程單實例的模型 2:避免使用全局變量,最好是使用局部變量,其實這本身也是一個好的編程習(xí)慣 3:應(yīng)該使用只讀的實例變量和靜態(tài)變量(就是前面加上final意為不可改變) 4:不要在Servlet上自己創(chuàng)建線程,因為Servlet容器已經(jīng)幫我們做好了。 5:如果要修改共享對象的時候記得要同步,盡量縮小同步的范圍(比喻修改Session時候直接使用synchronized(Session)即可),避免影響性能 |
|