1.在Java中有兩類線程:User Thread(用戶線程)、Daemon Thread(守護線程) ;
操作系統里面是沒有所謂的守護線程的概念,只有守護進程一說,但是Java語言機制是構建在JVM的基礎之上的,意思是Java平台把操作系統的底層給屏蔽起來,所以它可以在它自己的虛擬的平台里面構造出對自己有利的機制,而語言或者說平台的設計者多多少少是收到Unix思想的影響,而守護線程機制又是對JVM這樣的平台湊合,於是守護線程應運而生。
Daemon的作用是為其他線程的運行提供服務,比如說GC線程。其實User Thread線程和Daemon Thread守護線程本質上來說去沒啥區別的,唯一的區別之處就在虛擬機的離開時候:如果User Thread全部撤離,那么Daemon Thread也就沒啥線程好服務的了,所以虛擬機也就退出了。只要當前JVM實例中尚存在任何一個非守護線程沒有結束,守護線程就全部工作;只有當最后一個非守護線程結束時,守護線程隨着JVM一同結束工作。守護線程最典型的應用就是 GC (垃圾回收器)。
守護線程並非虛擬機內部可以提供,用戶也可以自行的設定守護線程,方法:
public final void setDaemon(boolean on) ;但是有幾點需要注意:
1)、thread.setDaemon(true)必須在thread.start()之前設置,否則會跑出一個IllegalThreadStateException異常。你不能把正在運行的常規線程設置為守護線程。 (備注:這點與守護進程有着明顯的區別,守護進程是創建后,讓進程擺脫原會話的控制+讓進程擺脫原進程組的控制+讓進程擺脫原控制終端的控制;所以說寄托於虛擬機的語言機制跟系統級語言有着本質上面的區別)
2)、 在Daemon線程中產生的新線程也是Daemon的。 (這一點又是與守護線程有着本質的區別了:守護進程fork()出來的子進程不再是守護進程,盡管它把父進程的進程相關信息復制過去了,但是子進程的進程的父進程不是init進程,所謂的守護進程本質上說就是“父進程掛掉,init收養,然后文件0,1,2都是/dev/null,當前目錄到/”)
3)、不是所有的應用都可以分配給Daemon線程來進行服務,比如讀寫操作或者計算邏輯。因為在Daemon Thread還沒來的及進行操作時,虛擬機可能已經退出了。
1 例子: 2 3 //完成文件輸出的守護線程任務 4 5 import java.io.*; 6 7 class TestRunnable implements Runnable{ 8 9 public void run(){ 10 11 try{ 12 13 Thread.sleep(1000);//守護線程阻塞1秒后運行 14 15 File f=new File("daemon.txt"); 16 17 FileOutputStream os=new FileOutputStream(f,true); 18 19 os.write("daemon".getBytes()); 20 21 } 22 23 catch(IOException e1){ 24 25 e1.printStackTrace(); 26 27 } 28 29 catch(InterruptedException e2){ 30 31 e2.printStackTrace(); 32 33 } 34 35 } 36 37 } 38 39 public class TestDemo2{ 40 41 public static void main(String[] args) throws InterruptedException 42 43 { 44 45 Runnable tr=new TestRunnable(); 46 47 Thread thread=new Thread(tr); 48 49 thread.setDaemon(true); //設置守護線程 50 51 thread.start(); //開始執行分進程 52 53 } 54 55 }
運行結果:文件daemon.txt中沒有"daemon"字符串。
但是如果把thread.setDaemon(true); //設置守護線程注釋掉,文件daemon.txt是可以被寫入daemon字符串的;
JRE判斷程序是否執行結束的標准是所有的前台執線程行完畢了,而不管后台線程的狀態,因此,在使用后台線程候一定要注意這個問題。
但是daemon Thread實際應用在那里呢?舉個例子,web服務器中的Servlet,容器啟動時后台初始化一個服務線程,即調度線程,負責處理http請求,然后每個請求過來調度線程從線程池中取出一個工作者線程來處理該請求,從而實現並發控制的目的;
而servlet采用單實例多線程模式開發,減少產生servlet實例的開銷。
Servlet 默認是單例模式,在web 容器中只創建一個實例,所以多個線程同時訪問servlet的時候,Servlet是線程不安全的
2.servlet容器維護一個線程池,里面放着工作者線程來相應請求,同時還有一個調度線程來管理工作者線程。當容器收到一個servlet請求,調度線程就從線程池中取出一個工作者線程,該工作者線程將處理這個請求,做法是執行servlet的service方法;當這個線程執行時,收到另一個請求,調度線程就在線程池中取出另一個工作者線程來響應新的請求。容器不關心請求是否訪問的是同一個servlet,當多個請求同時訪問同一個servlet時,這個servlet的service方法將在多線程中並發執行。並發執行必然會出現同步問題,也就是線程的安全問題;
3.Servlet容器如何同時來處理多個請求
工作者線程Work Thread:執行代碼的一組線程
調度線程Dispatcher Thread:每個線程都具有分配給它的線程優先級,線程是根據優先級調度執行的
Servlet采用多線程來處理多個請求同時訪問。servlet依賴於一個線程池來服務請求。線程池實際上是一系列的工作者線程集合。Servlet使用一個調度線程來管理工作者線程.
當容器收到一個Servlet請求,調度線程從線程池中選出一個工作者線程,將請求傳遞給該工作者線程,然后由該線程來執行Servlet的service方法。當這個線程正在執行的時候,容器收到另外一個請求,調度線程同樣從線程池中選出另一個工作者線程來服務新的請求,容器並不關心這個請求是否訪問的是同一個Servlet.當容器同時收到對同一個Servlet的多個請求的時候,那么這個Servlet的service()方法將在多線程中並發執行。
Servlet容器默認采用單實例多線程的方式來處理請求,這樣減少產生Servlet實例的開銷,提升了對請求的響應時間,對於Tomcat可以在server.xml中通過元素設置線程池中線程的數目。
就實現來說:
調度者線程類所擔負的責任是線程的調度,只需要利用自己的屬性完成自己的責任。所以該類是承擔了責任的,並且該類的責任又集中到唯一的單體對象中。
而其他對象又依賴於該特定對象所承擔的責任,我們就需要得到該特定對象。那該類就是一個單例模式的實現了
4.如何開發線程安全的Servlet
1).變量的線程安全:這里的變量指字段和共享數據(如表單參數值)。
a,將 參數變量本地化。多線程並不共享局部變量.所以我們要盡可能的在servlet中使用局部變量。 例如:String user = “”;
user = request.getParameter(“user”);
b,使用同步塊Synchronized,防止可能異步調用的代碼塊。這意味着線程需要排隊處理。 在使用同步塊的時候要盡可能的縮小同步代碼的范圍,不要直接在sevice方法和響應方法上使用同步,這樣會嚴重影響性能
2) .屬性的線程安全:ServletContext,HttpSession,ServletRequest對象中屬性
ServletContext:(線程是不安全的)
ServletContext是可以多線程同時讀/寫屬性的,線程是不安全的。要對屬性的讀寫進行同步處理或者進行深度Clone()。
所以在Servlet上下文中盡可能少量保存會被修改(寫)的數據,可以采取其他方式在多個Servlet中共享,比方我們可以使用單例模式來處理共享數據。
HttpSession:(線程是不安全的)
HttpSession對象在用戶會話期間存在,只能在處理屬於同一個Session的請求的線程中被訪問,因此Session對象的屬性訪問理論上是線程安全的。
當用戶打開多個同屬於一個進程的瀏覽器窗口,在這些窗口的訪問屬於同一個Session,會出現多次請求,需要多個工作線程來處理請求,可能造成同時多線程讀寫屬性。
這時我們需要對屬性的讀寫進行同步處理:使用同步塊Synchronized和使用讀/寫器來解決。
ServletRequest:(線程是安全的)
對於每一個請求,由一個工作線程來執行,都會創建有一個新的ServletRequest對象,所以ServletRequest對象只能在一個線程中被訪問。ServletRequest是線程安全的。
注意:ServletRequest對象在service方法的范圍內是有效的,不要試圖在service方法結束后仍然保存請求對象的引用。
3).使用同步的集合類:
使用Vector代替ArrayList,使用Hashtable代替HashMap。
4).不要在Servlet中創建自己的線程來完成某個功能。
Servlet本身就是多線程的,在Servlet中再創建線程,將導致執行情況復雜化,出現多線程安全問題。
5).在多個servlet中對外部對象(比方文件)進行修改操作一定要加鎖,做到互斥的訪問。
6).javax.servlet.SingleThreadModel接口是一個標識接口,此接口沒有方法,跟Serializable接口一樣只是一個標識接口,如果一個Servlet實現了這個接口,那Servlet容器將保證在一個時刻僅有一個線程可以在給定的servlet實例的service方法中執行。將其他所有請求進行排隊。
服務器可以使用多個實例來處理請求,代替單個實例的請求排隊帶來的效益問題。服務器創建一個Servlet類的多個Servlet實例組成的實例池,對於每個請求分配Servlet實例進行響應處理,之后放回到實例池中等待下此請求。這樣就造成並發訪問的問題。
此時,局部變量(字段)也是安全的,但對於全局變量和共享數據是不安全的,需要進行同步處理。而對於這樣多實例的情況SingleThreadModel接口並不能解決並發訪問問題。
SingleThreadModel接口在servlet規范中已經被廢棄了