上面是隨手寫的一個類,沒有任何意義,只是為了強調一些概念,這和這個主題很有關系: 1) java中的變量的分類: a. 實例變量 b. 局部變量 c. 靜態變量 本篇并不是java的基礎教程,因此不會詳盡到每個基礎知識點(下面的內容是區分對象和對象變量這兩個概念的,<<core java2>>嚴格區分,不過大多數教材并不過于苛刻的區別它們) a. 實例變量:屬于每個對象,也就是每個對象都有一份此變量的副本。上面的variable1就指向實例變量。 b. 局部變量:工作在某個作用域,離開作用域之后成為垃圾。上面的variable3就指向局部變量,局部變量存在于方法中。 c. 靜態變量:屬于某個類,也就是所有對象共有一個副本。上面的variable2就指向靜態變量。 2) servlet容器的實例化: servlet如何被實例化的,不是我們所關心的,我們關心的是servlet被實例化了多少次,是每一次請求都實例化還是僅僅實例化一次?要解答這個問題太簡單了:
打開瀏覽器,訪問指定的servlet,不斷點刷新,結果是: 實例化了 1 次 被訪問了1次 被訪問了2次 被訪問了3次 不要關閉瀏覽器,再開一個瀏覽器訪問,不斷點刷新,結果是: 被訪問了4次 被訪問了5次 被訪問了6次 被訪問了7次 被訪問了8次 可見對不僅僅是對同一個用戶,對于其他用戶也只實例化一個對象。 也就是說,Tomcat僅僅實例化一次servlet,產生一個對象。 那servlet容器是如何相應多用戶同時訪問的呢?回答:多線程。為每次訪問都用一個獨立的線程運行doGet或者是doPost方法。也就是servlet容器的內部實現可能是這樣的:
這沒什么問題,但是一些初級程序員可能放這樣的錯誤:
會有什么結果?單A訪問這個Test,通過response.getWriter()得到一個實例,保存在out中,假定這時候B在A之前執行完了response.getWriter(),并開始執行out.println(request.getRemoteAddr()),這時候out保存的并不是B自己通過getWriter()方法得到的PrintWriter對象,而是A運行getWriter的結果,那么B就輸出了A的地址。再看看下面的例子: 建立一個webapp,名字叫做web,建立sendname.html的HTML文件,內容如下:
建立一個servlet叫Test,內容如下:
通過瀏覽器打開2個sendname.html(http://localhost:8080/web/sendname.html),分別輸入名字A和B(間隔不要超過5秒): 結果2個頁面顯示分別是: I input B I am B I input A I am B 這就是Servlet中的多線程問題。解決的辦法很簡單,就是不用實例變量,至少不在絕對必要的時候用。 在JSP中這樣的問題更加突出,因為使用<%! %>進行的變量聲明得到的是一個實例變量,如果一定要定義,那么就使用synchronized,在使用實例變量的方法前加上synchronized,它也是不推薦使用的,下面是一個例子:
在<%! %>中定義的方式也會被多線程調用。 對于JSP頁面,除了使用synchronized,還可以使用page指令元素的isThreadSafe來控制整個頁面是否可以多線程訪問。