這個問題網上一直沒有搜到很詳細的解釋,也可能是高人的解釋不符合我的理解方式。所以自己到網上搜集了寫資料再加自己的想法,隨便寫了點東西發到論壇上,希望大家給予修正意見,看我是否理解對了。
一般servlet在jvm中只有個對象,當多個請求來請求一個jsp頁面的時候,實際上都是調用這個jsp編譯好的servlet類doPost或者doGet方法。
現在我就類比一個servlet的調用過程:
new Runnalbe{ public run(){ Request requset = new Request(); Resposne response = new Response(); //servlet對象只有一個,是容器自動產生的,這裡類比一個servlet的調用過程。 servlet.doPost (request,response); } }
當有多個請求過來的時候,相當於多個線程來執行這段代碼。上面那個servlet的實作類別HelloServlet:
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是單一實例多線程運行方式,所以物件變數線程不安全,局部變數安全執行緒。