標籤:
- public class MyClass {
- private String variable1 ;
- private static String variable2 ;
- public MyClass(){
- }
- public void method(){
- String variable3;
- }
- }
上面是隨手寫的一個類,沒有任何意義,只是為了強調一些概念,這和這個主題很有關係:
1) java中的變數的分類:
a. 執行個體變數
b. 局部變數
c. 靜態變數
本篇並不是java的基礎教程,因此不會詳盡到每個基礎知識點(下面的內容是區分對象和物件變數這兩個概念的,<<core java2>>嚴格區分,不過大多數教材並不過於苛刻的區別它們)
a. 執行個體變數:屬於每個對象,也就是每個對象都有一份此變數的副本。上面的variable1就指向執行個體變數。
b. 局部變數:工作在某個範圍,離開範圍之後成為垃圾。上面的variable3就指向局部變數,局部變數存在於方法中。
c. 靜態變數:屬於某個類,也就是所有對象共有一個副本。上面的variable2就指向靜態變數。
2) servlet容器的執行個體化:
servlet如何被執行個體化的,不是我們所關心的,我們關心的是servlet被執行個體化了多少次,是每一次請求都執行個體化還是僅僅執行個體化一次?要解答這個問題太簡單了:
[java] view plaincopy
- public class Test extends HttpServlet {
- private static int count = 0;
- private int num = 0;
-
- public Test() {
- super();
- count ++;
- System.out.println("執行個體化了 "+count+" 次");
- }
-
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- response.setContentType("text/html");
- num ++;
- System.out.println("被訪問了" + num+"次");
- }
- }
開啟瀏覽器,訪問指定的servlet,不斷點重新整理,結果是:
執行個體化了 1 次
被訪問了1次
被訪問了2次
被訪問了3次
不要關閉瀏覽器,再開一個瀏覽器訪問,不斷點重新整理,結果是:
被訪問了4次
被訪問了5次
被訪問了6次
被訪問了7次
被訪問了8次
可見對不僅僅是對同一個使用者,對於其他使用者也只執行個體化一個對象。
也就是說,Tomcat僅僅執行個體化一次servlet,產生一個對象。
那servlet容器是如何相應多使用者同時訪問的呢?回答:多線程。為每次訪問都用一個獨立的線程運行doGet或者是doPost方法。也就是servlet容器的內部實現可能是這樣的:
[java] view plaincopy
- class . extends Thread{
- public void run(){
- if(request 為 get)
- servlet.doGet(); // servlet指向你請求的servlet類的執行個體
- else if(request 為 post)
- servlet.doPost();
- }
- }
這沒什麼問題,但是一些初級程式員可能放這樣的錯誤:
[java] view plaincopy
- public class Test extends HttpServlet {
- private PrintWriter out ;
-
- public Test() {
- super();
- }
-
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- response.setContentType("text/html");
- out = response.getWriter();
- out.println(request.getRemoteAddr());
- }
- }
會有什麼結果?單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檔案,內容如下:
[java] view plaincopy
- <html>
- <head></head>
- <body>
- <form action="/web/Test">
- <input type="text" name="name"/>
- <input type="submit" />
- </form>
- </body>
- </html>
建立一個servlet叫Test,內容如下:
[java] view plaincopy
- public class Test extends HttpServlet {
- private String name ;
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- response.setContentType("text/html");
- PrintWriter out = response.getWriter();
- name = request.getParameter("name");
- out.println("I input "+name);
- try{Thread.sleep(5000);}catch(Exception ex){}
- out.println("I am "+name);
- out.flush();
- out.close();
- }
- }
通過瀏覽器開啟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,它也是不推薦使用的,下面是一個例子:
[java] view plaincopy
- <%@ page contentType="text/html;charset=GB2312" %>
- <HTML>
- <BODY>
-
- <!--number 為臨界區-->
- <%! int number=0;
- synchronized void countPeople()
- { number++;
- }
- %>
- <% countPeople();
- %>
- <P><P>您是第
- <%=number%>
- 個訪問本站的客戶。
- </BODY>
- </HTML>
在<%! %>中定義的方式也會被多線程調用。
對於JSP頁面,除了使用synchronized,還可以使用page指令元素的isThreadSafe來控制整個頁面是否可以多線程訪問。
【轉】多使用者訪問Servlet,servlet單一實例多線程