Servlet是web體系裡面最重要的部分,下麵羅列幾道常見的面試題,小伙伴們一定要好好記住哈。 1.Servlet是單例的嗎,如何證明? Servlet一般都是單例的,並且是多線程的。如何證明Servlet是單例模式呢?很簡單,重寫Servlet的init方法,或者添加一個構造方法。然後,在web ...
Servlet是web體系裡面最重要的部分,下麵羅列幾道常見的面試題,小伙伴們一定要好好記住哈。
1.Servlet是單例的嗎,如何證明?
Servlet一般都是單例的,並且是多線程的。如何證明Servlet是單例模式呢?很簡單,重寫Servlet的init方法,或者添加一個構造方法。然後,在web.xml中配置。如:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>web.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
然後是MyServlet
public class MyServlet extends HttpServlet{
public MyServlet(){
System.out.println("MyServlet構造函數調用了");
}
@Override
public void init() throws ServletException {
System.out.println("MyServlet初始化");
}
}
啟動Tomcat,不管你訪問多少次這個Servlet,init方法和構造器都只會執行1次。
2.如何讓Servlet變成多例
方法1.實現 SingleThreadModel 介面(不推薦,官方已經將這個介面廢棄)
public class MyServlet extends HttpServlet implements SingleThreadModel{
public MyServlet(){
System.out.println("MyServlet構造函數調用了");
}
@Override
public void init() throws ServletException {
System.out.println("MyServlet初始化");
}
}
SingleThreadModel
的意思是“單線程模式”,如果servlet實現了該介面,會確保不會有兩個線程同時執行servlet的service方法。
servlet容器通過同步化訪問servlet的單實例來保證,也可以通過維持servlet的實例池,對於新的請求會分配給一個空閑的servlet。源碼中,最多會生成20個實例。
方法2. 在web.xml中多配置一個Servlet
哪怕是同一個Servlet,你在web.xml中配置幾個,就會有幾個實例。
3.你能證明Servlet線程不安全嗎?
Servlet預設是線程不安全的!
Servlet體繫結構是建立在Java多線程機制之上的,它的生命周期是由Web容器負責的。
當客戶端第一次請求某個Servlet時,Servlet容器將會根據web.xml配置文件實例化這個Servlet類。
當有新的客戶端請求該Servlet時,一般不會再實例化該Servlet類,也就是有多個線程在使用這個實例。
Servlet容器會自動使用線程池等技術來支持系統的運行。
當兩個或多個線程同時訪問同一個Servlet時,可能會發生多個線程同時訪問同一資源的情況,數據可能會變得不一致。
所以在用Servlet構建的Web應用時如果不註意線程安全的問題,會使所寫的Servlet程式有難以發現的錯誤。
下麵舉一個例子來說明,為什麼Servlet是線程不安全的。
public class MyServlet extends HttpServlet{
String message;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
message = req.getParameter("message");
PrintWriter out = resp.getWriter();
//故意延時5秒鐘,使得下一次請求過來的時候,message的值還沒有返回就被覆蓋了
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.write(message);
out.flush();
out.close();
}
}
打開兩個瀏覽器,分別訪問:
http://localhost:8080/web/hello?message=jack
http://localhost:8080/web/hello?message=rose
因為有5秒的延時,所以可能就會出現第一個Servlet還沒返回呢,第二個Servlet就進來了。於是,把message的值給衝掉了。如下圖
石錘了,Servlet是線程不安全的。
4.你怎麼設計一個線程安全的Servlet?
1.最直接的辦法,就是用上面的SingleThreadModel介面
既然單例會有共用實例變數導致線程不安全的問題,那就改成多例的唄。
但是,這個介面都已經被官方廢棄了,這就說明官方也不推薦這麼做。原因很簡單,那就是這樣一來會有很多個實例,性能的代價太大了。
-
用同步鎖
這也是非常容易想到的辦法,把當前對象鎖起來,不返回不給其他用戶插入(怎麼有點怪怪的?)
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
synchronized(this){
message = req.getParameter("message");
PrintWriter out = resp.getWriter();
//故意延時5秒鐘,使得下一次請求過來的時候,message的值還沒有釋放
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.write(message);
out.flush();
out.close();
}
}
這樣的代價就是等待時間更長了,參考火車上的的衛生間,這就是同步鎖。
-
儘量別用實例變數,用局部變數代替