這個問題網上一直沒有搜到很詳細的解釋,也可能是高人的解釋不符合我的理解方式。所以自己到網上搜集了寫資料再加自己的想法,隨便寫了點東西發到論壇上,希望大家給予修正意見,看我是否理解對了。
一般servlet在jvm中只有個對象,當多個請求來請求一個jsp頁面的時候,實際上都是調用這個jsp編譯好的servlet類doPost或者doGet方法。
現在我就類比一個servlet的調用過程
Java代碼
- new Runnalbe{
- public run(){
- Request requset = new Request();
- Resposne response = new Response();
- //servlet對象只有一個,是容器自動產生的,這裡類比一個servlet的調用過程。
- servlet.doPost (request,response);
- }
- }
new Runnalbe{public run(){Request requset = new Request();Resposne response = new Response(); //servlet對象只有一個,是容器自動產生的,這裡類比一個servlet的調用過程。servlet.doPost(request,response);}}
當有多個請求過來的時候,相當於多個線程來執行這段代碼。上面那個servlet的實作類別HelloServlet:
Java代碼
- public class HelloServlet extends HttpServlet {
- private int j =0;
- public void doPost(HttpServletRequest request, HttpServletResponse response){
- int i=0;
- i++;
- j++;
- //這裡的i和j那個是安全執行緒的那個不是呢,後面我們將從線程的堆棧,和jvm的堆的概念來解釋這個問題
- //request 和 response 對象是不是安全執行緒的
- }
- }
public class HelloServlet extends HttpServlet {private int j =0; public void doPost(HttpServletRequest request, HttpServletResponse response){ int i=0; i++; j++;//這裡的i和j那個是安全執行緒的那個不是呢,後面我們將從線程的堆棧,和jvm的堆的概念來解釋這個問題//request 和 response 對象是不是安全執行緒的 }}
JVM是基於堆棧的虛擬機器.JVM為每個新建立的線程都分配一個堆棧(這裡的堆棧不是指堆).也就是說,對於一個Java程式來說,它的運行
就是通過對堆棧的操作來完成的。堆棧以幀為單位儲存線程的狀態。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。
我們知道,某個線程正在執行的方法稱為此線程的當前方法.我們可能不知道,當前方法使用的幀稱為當前幀。當線程啟用一個
Java方法,JVM就會線上程的Java堆棧裡新壓入一個幀。這個幀自然成為了當前幀.在此方法執行期間,這個幀將用來儲存參數,局部變數,中間計算過程和其他資料.這個幀在這裡和編譯原理中的活動紀錄的概念是差不多的。
這裡還要補充一下堆的概念:
堆(heap)是放執行個體和數組的,JAVA裡面沒有全域變數這個概念,所有變數都是以類的屬性或者參數等形式存在的。GC是自動回收.但是數組和類的引用是放在堆棧中。
學過彙編的可能都知道,資料是是儲存在棧內的,執行的代碼邏輯是可以共用的。當多個線程來訪問同一個方法的時候,共用同一段代碼邏輯,但是方法對應的資料是儲存在各自的堆棧(stack)中,如局部變數和參數還有對象的引用(局部變數和參數也可能是對象的引用)。所以多線程並發的情況下,出現不同步的現象主要是因為各自堆棧存放的某些資料是共用的,說白了就是同一個資料的引用(不是copy)被存放在不同的堆棧中。 例如類X的對象A被多個線程訪問,他的引用被儲存在多個線程的堆棧中,當多個線程訪問A對象的某個屬性b的時候如果不加鎖就會出現不同步的現象。所以為了避免這種情況發生一是加鎖,二就是為每個線程都產生一個類X的對象,這樣每個線程的堆棧中存放的類X引用所對應的堆中的對象都不一樣,當然就不存在共用的問題。
現在我們回到開始那個例子,可以很好的分析出參數request,respone,i是安全執行緒的,而j是線程不安全的;
為什嗎?request,respone是安全執行緒的是因為每個線程對應的request,respone對象都是不一樣的,不存在共用問題。i是安全執行緒的是應為i是局部變數,每個線程的堆棧中存放的值也是各自獨立的。
j線程不安全是應為它是類HelloServlet的屬性,找了很多資料都不能說清楚它到底放在那裡,從實際效果來看,它是不安全的,所以應該是放在和類對象一起放在堆裡面的,堆裡面估計是複製了一份過來,因為HelloServlet對多個線程而言只有
一個執行個體,所以存在共用問題。
一般來說Servlet都是單例的。
public class TestServletSingle extends HttpServlet {<br /> protected void doGet(HttpServletRequest request,<br /> HttpServletResponse response) throws ServletException, IOException {<br /> try {<br /> Thread.sleep(5000);<br /> } catch (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> response.getOutputStream().println(this.hashCode());<br /> } </p><p> protected void doPost(HttpServletRequest request,<br /> HttpServletResponse response) throws ServletException, IOException {<br /> doGet(request, response);<br /> }<br />}<br />
你在5秒內開啟N個瀏覽器視窗試試,返回的都是相同的Hashcode。只有在context reload以後,Hashcode才發生變化。因為原來的servlet object銷毀了,容器又建立了一個。
然而,Servlet的單例不由class來決定,而是由您在web.xml裡配置的servlet-name來決定。也就是說每一個servlet-name只有1個單例。當你有3個不同的serlvet-name指定了相同的1個servlet-class時,容器會產生3個不同的servlet object。只有3個。
見 javaEye 的 主題:Servlet的安全執行緒問題 的討論