From Gossip@caterpillar

JSP/Servlet: Servlet 執行緒安全



基本上Servlet容器會為每一個Servlet註冊名稱實例化一個物件,來自用戶端的請求會以一個執行緒來存取這個物件,如果有多個請求同時到達,就會有多個執行緒存取同一個Servlet實例,而這所引發的就是執行緒的安全問題。

如果您的Servlet實例之方法都是使用區域變數,這並不會有太大的問題,每一個執行緒對方法的呼叫會使用自己的區域變數,然而如果您的 Servlet方法中有共用一些field成員或是靜態成員,在多個執行緒同時存取時,就會有執行緒安全問題,例如一個簡單的計數器問題:

  • ServletDemo.java
package onlyfun.caterpillar; 

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class ServletDemo extends HttpServlet {
private int count = 0;

public void doGet(HttpServletRequest req,
HttpServletResponse res)
throws ServletException, IOException {
PrintWriter out = res.getWriter();
count++;
out.println("This servlet is accessd " + count +
"times");
}
}

在上面這個Servlet中,由於count是field成員,在Servlet載入後,只有一個Servlet實例,每個請求以一個執行緒來存取這個實 例,有可能發生在前一個執行緒正在執行out.println(),而後一個執行緒正好執行count++,這會導致 count的計數不連續的情況發生,這個情況在這個例子中並不致於引發多大的問題,但它所代表的是多執行緒存取同一個Servlet的執行緒安全問題。

要求執行緒安全最基本的,就是對共用資源作同步化,例如對doGet()使用synchronized關鍵字:
public synchronized void doGet(HttpServletRequest req,
                                HttpServletResponse res)
 

或者是:
....
    PrintWriter out = res.getWriter();
    synchronized(this) {
        count++;
        out.println("This servlet is accessd " + count +
                    "times");
    }
....
 

同步化所帶來的是延遲,當同步化的區塊被鎖定時,其他的執行緒必須等待鎖定的解除,這所代表的就是對使用者請求的延遲,在伺服器上這點延遲在用戶端多時就 會導致使用者必須花費長時間來等待回應,可以將同步化區塊儘量縮小來減少延遲,或者是完全使用區域變數,使得執行緒之間不共用某些資源,然後不共用資源所 帶來的,就是某些資訊將無法實現持續性,為了實現持續性可能必須額外花費一些手續。

回顧一下JSP中在<%! 與 %>之間宣告的變數,在生成Servlet之後,就是生成一個field成員,所以在<%! 與 %>之間宣告變數,也必須小心執行緒安全問題,通常並不建議這麽宣告變數,而是在<% 與 %>之間宣告變數,因為在轉為Servlet之間,它是_jspService()中的一個區域變數,即使在多個用戶端執行緒同時存取時,也不致於 發生執行緒共用資源的問題。