servlet多線程分析

Servlet體系結構是建立在Java多線程機制之上的,它的生命週期是由Web容器負責的。當客戶端第一次請求某個Servlet時,Servlet容器將會根據web.xml配置文件實例化這個Servlet類。當有新的客戶端請求該Servlet時,一般不會再實例化該Servlet類,也就是有多個線程在使用這個實例。 這樣,當兩個或多個線程同時訪問同一個Servlet時,可能會發生多個線程同時訪問同一資源的情況,數據可能會變得不一致。所以在用Servlet構建的Web應用時如果不注意線程安全的問題,會使所寫的Servlet程序有難以發現的錯誤。

實例變量不正確的使用是造成Servlet線程不安全的主要原因。下面針對該問題給出了三種解決方案並對方案的選取給出了一些參考性的建議。

  1、實現 SingleThreadModel 接口

  該接口指定了系統如何處理對同一個Servlet的調用。如果一個Servlet被這個接口指定,那麼在這個Servlet中的service方法將不會有兩個線程被同時執行,當然也就不存在線程安全的問題。這種方法只要將前面的Concurrent Test類的類頭定義更改爲:

Public class Concurrent Test extends HttpServlet implements SingleThreadModel {
     …………

  2、同步對共享數據的操作

  使用synchronized 關鍵字能保證一次只有一個線程可以訪問被保護的區段,在本論文中的Servlet可以通過同步塊操作來保證線程的安全。同步後的代碼如下: 

…………
Public class Concurrent Test extends HttpServlet { …………
Username = request.getParameter ("username"); 
Synchronized (this){
Output = response.getWriter (); 
Try {
Thread. Sleep (5000);
} Catch (Interrupted Exception e){}
output.println("用戶名:"+Username+"
"); 

}
}

  3、避免使用實例變量

  本實例中的線程安全問題是由實例變量造成的,只要在Servlet裏面的任何方法裏面都不使用實例變量,那麼該Servlet就是線程安全的。

  修正上面的Servlet代碼,將實例變量改爲局部變量實現同樣的功能,代碼如下:

…… 
Public class Concurrent Test extends HttpServlet {public void service (HttpServletRequest request, HttpServletResponse 
Response) throws ServletException, IOException {
Print Writer output; 
String username;
Response.setContentType ("text/html; charset=gb2312");
…… 

}

  對上面的三種方法進行測試,可以表明用它們都能設計出線程安全的Servlet程序。但是,如果一個Servlet實現了SingleThreadModel接口,Servlet引擎將爲每個新的請求創建一個單獨的Servlet實例,這將引起大量的系統開銷。SingleThreadModel在Servlet2.4中已不再提倡使用;同樣如果在程序中使用同步來保護要使用的共享的數據,也會使系統的性能大大下降。這是因爲被同步的代碼塊在同一時刻只能有一個線程執行它,使得其同時處理客戶請求的吞吐量降低,而且很多客戶處於阻塞狀態。另外爲保證主存內容和線程的工作內存中的數據的一致性,要頻繁地刷新緩存,這也會大大地影響系統的性能。所以在實際的開發中也應避免或最小化 Servlet 中的同步代碼;在Serlet中避免使用實例變量是保證Servlet線程安全的最佳選擇。從Java 內存模型也可以知道,方法中的臨時變量是在棧上分配空間,而且每個線程都有自己私有的棧空間,所以它們不會影響線程的安全。

 


 

補充:

servlet存在的多線程問題
實例變量:   實例變量是在堆中分配的,並被屬於該實例的所有線程共享,所以不是線程安全的. 
JSP系統提供的8個類變量:
JSP中用到的OUT,REQUEST,RESPONSE,SESSION,CONFIG,PAGE,PAGECONXT是線程安全的,APPLICATION在整個系統內被使用,所以不是線程安全的. 
局部變量:   局部變量在堆棧中分配,因爲每個線程都有它自己的堆棧空間,所以是線程安全的. 
靜態類:       靜態類不用被實例化,就可直接使用,也不是線程安全的. 
外部資源:   在程序中可能會有多個線程或進程同時操作同一個資源(如:多個線程或進程同時對一個文件進行寫操作).

此時也要注意同步問題. 使它以單線程方式執行,這時,仍然只有一個實例,所有客戶端的請求以串行方式執行。這樣會降低系統的性能
對於存在線程不安全的類,如何避免出現線程安全問題:
1、採用synchronized同步。缺點就是存在堵塞問題。
2、使用ThreadLocal(實際上就是一個HashMap),這樣不同的線程維護自己的對象,線程之間相互不干擾。


ThreadLocal的設計 
首先看看ThreadLocal的接口: 
Object get() ; // 返回當前線程的線程局部變量副本 protected Object 
initialValue(); // 返回該線程局部變量的當前線程的初始值                    
void set(Object value); // 設置當前線程的線程局部變量副本的值 
  ThreadLocal有3個方法,其中值得注意的是initialValue(),該方法是一個protected 
的方法,顯然是爲了子類重寫而特意實現的。該方法返回當前線程在該線程局部變量的初始 
值,這個方法是一個延遲調用方法,在一個線程第1次調用get()或者set(Object)時才執行 
,並且僅執行1次。ThreadLocal中的確實實現直接返回一個null: 
protected Object initialValue() { return null; } 
  ThreadLocal是如何做到爲每一個線程維護變量的副本的呢?其實實現的思路很簡單, 
在ThreadLocal類中有一個Map,用於存儲每一個線程的變量的副本。比如下面的示例實現:

public class ThreadLocal 

 private Map values = Collections.synchronizedMap(new HashMap()); 
 public Object get() 
 { 
  Thread curThread = Thread.currentThread(); 
  Object o = values.get(curThread); 
  if (o == null && !values.containsKey(curThread)) 
  { 
   o = initialValue(); 
   values.put(curThread, o); 
  } 
  return o; 
 }

 public void set(Object newValue) 
 { 
  values.put(Thread.currentThread(), newValue); 
 }

 public Object initialValue() 
 { 
  return null; 
 } 
}

  當然,這並不是一個工業強度的實現,但JDK中的ThreadLocal的實現總體思路也類似於此。 
ThreadLocal的使用 
  如果希望線程局部變量初始化其它值,那麼需要自己實現ThreadLocal的子類並重寫該 
方法,通常使用一個內部匿名類對ThreadLocal進行子類化,比如下面的例子,SerialNum類 
爲每一個類分配一個序號: 
public class SerialNum 

 // The next serial number to be assigned 
 private static int nextSerialNum = 0; 
 private static ThreadLocal serialNum = new ThreadLocal() 
 { 
  protected synchronized Object initialValue() 
  { 
   return new Integer(nextSerialNum++); 
  } 
 };

 public static int get() 
 { 
  return ((Integer) (serialNum.get())).intValue(); 
 } 
}

  SerialNum類的使用將非常地簡單,因爲get()方法是static的,所以在需要獲取當前線 
程的序號時,簡單地調用:

int serial = SerialNum.get(); 即可。 
  在線程是活動的並且ThreadLocal對象是可訪問的時,該線程就持有一個到該線程局部 
變量副本的隱含引用,當該線程運行結束後,該線程擁有的所以線程局部變量的副本都將失 
效,並等待垃圾收集器收集。 
ThreadLocal與其它同步機制的比較 
  ThreadLocal和其它同步機制相比有什麼優勢呢?ThreadLocal和其它所有的同步機制都 
是爲了解決多線程中的對同一變量的訪問衝突,在普通的同步機制中,是通過對象加鎖來實 
現多個線程對同一變量的安全訪問的。這時該變量是多個線程共享的,使用這種同步機制需 
要很細緻地分析在什麼時候對變量進行讀寫,什麼時候需要鎖定某個對象,什麼時候釋放該 
對象的鎖等等很多。所有這些都是因爲多個線程共享了資源造成的。ThreadLocal就從另一 
個角度來解決多線程的併發訪問,ThreadLocal會爲每一個線程維護一個和該線程綁定的變 
量的副本,從而隔離了多個線程的數據,每一個線程都擁有自己的變量副本,從而也就沒有 
必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時 
,可以把不安全的整個變量封裝進ThreadLocal,或者把該對象的特定於線程的狀態封裝進 
ThreadLocal。 
  由於ThreadLocal中可以持有任何類型的對象,所以使用ThreadLocal get當前線程的值 
是需要進行強制類型轉換。但隨着新的Java版本(1.5)將模版的引入,新的支持模版參數 
的ThreadLocal<T>類將從中受益。也可以減少強制類型轉換,並將一些錯誤檢查提前到了編 
譯期,將一定程度地簡化ThreadLocal的使用。 
總結 
    當然ThreadLocal並不能替代同步機制,兩者面向的問題領域不同。同步機制是爲了同 
步多個線程對相同資源的併發訪問,是爲了多個線程之間進行通信的有效方式;而 
ThreadLocal是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源(變量) 
,這樣當然不需要對多個線程進行同步了。所以,如果你需要進行多個線程之間進行通信, 
則使用同步機制;如果需要隔離多個線程之間的共享衝突,可以使用ThreadLocal
,這將極 
大地簡化你的程序,使程序更加易讀、簡潔。

ThreadLocal常見用途:
存放當前session用戶
存放一些context變量,比如webwork的ActionContext
存放session,比如Spring hibernate orm的session


例子:用 ThreadLocal 實現每線程 Singleton
        線程局部變量常被用來描繪有狀態“單子”(Singleton) 或線程安全的共享對象,或者是通過把不安全的整個變量封裝進 ThreadLocal,或者是通過把對象的特定於線程的狀態封裝進 ThreadLocal。例如,在與數據庫有緊密聯繫的應用程序中,程序的很多方法可能都需要訪問數據庫。在系統的每個方法中都包含一個 Connection 作爲參數是不方便的 — 用“單子”來訪問連接可能是一個雖然更粗糙,但卻方便得多的技術。然而,多個線程不能安全地共享一個 JDBC Connection。如清單 3 所示,通過使用“單子”中的 ThreadLocal,我們就能讓我們的程序中的任何類容易地獲取每線程 Connection 的一個引用。這樣,我們可以認爲 ThreadLocal 允許我們創建每線程單子。
例:把一個 JDBC 連接存儲到一個每線程 Singleton 中 
public class ConnectionDispenser { 
  private static class ThreadLocalConnection extends ThreadLocal {
           public Object initialValue() {
                  return DriverManager.getConnection(ConfigurationSingleton.getDbUrl());
           }
   }
    private ThreadLocalConnection conn = new ThreadLocalConnection();
    public static Connection getConnection() {
            return (Connection) conn.get();
   }
}

注意:
理論上來說,ThreadLocal是的確是相對於每個線程,每個線程會有自己的ThreadLocal。但是上面已經講到,一般的應用服務器都會維護一套線程池。因此,不同用戶訪問,可能會接受到同樣的線程。因此,在做基於TheadLocal時,需要謹慎,避免出現ThreadLocal變量的緩存,導致其他線程訪問到本線程變量。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章