java線程 同步與異步 線程池 1)多線程並發時,多個線程同時請求同一個資源,必然導致此資源的數據不安全,A線程修改了B線 程的處理的數據,而B線程又修改了A線程處理的數理。顯然這是由於全局資源造成的,有時為了解 決此問題,優先考慮使用局部變量,退而求其次使用同步代碼塊,出於這樣的安全考慮就必須犧牲 系統處理性能,加在多線程並發時資源掙奪最激烈的地方,這就實現了線程的同步機制 同步:A線程要請求某個資源,但是此資源正在被B線程使用中,因為同步機制存在,A線程請求 不到,怎么辦,A線程只能等待下去 異步:A線程要請求某個資源,但是此資源正在被B線程使用中,因為沒有同步機制存在,A線程 仍然請求的到,A線程無需等待 顯然,同步最最安全,最保險的。而異步不安全,容易導致死鎖,這樣一個線程死掉就會導致整個 進程崩潰,但沒有同步機制的存在,性能會有所提升 java中實現多線程 1)繼承Thread,重寫里面的run方法 2)實現runnable接口 Doug Lea比較推薦后者,第一,java沒有單繼承的限制第二,還可以隔離代碼 線程池 要知道在計算機中任何資源的創建,包括線程,都需要消耗系統資源的。在WEB服務中,對於web服 務器的響應速度必須要盡可能的快,這就容不得每次在用戶提交請求按鈕后,再創建線程提供服務 。為了減少用戶的等待時間,線程必須預先創建,放在線程池中,線程池可以用HashTable這種數 據結構來實現,看了Apach HTTP服務器的線程池的源代碼,用是就是HashTable,KEY用線程對象, value 用ControlRunnable,ControlRunnable是線程池中唯一能干活的線程,是它指派線程池中的 線程對外提供服務。 出於安全考慮,Apach HTTP服務器的線程池它是同步的。聽說weblogic有異步的實現方式,沒有研 究過,不敢確定 --------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------- 一、關鍵字: thread(線程)、thread-safe(線程安全)、intercurrent(並發的) synchronized(同步的)、asynchronized(異步的)、 volatile(易變的)、atomic(原子的)、share(共享) 二、總結背景: 一次讀寫共享文件編寫,嚯,好家伙,竟然揪出這些零碎而又是一路的知識點。於是乎,Google和 翻閱了《Java參考大全》、《Effective Java Second Edition》,特此總結一下供日后工作學習參 考。 三、概念: 1、 什么時候必須同步?什么叫同步?如何同步? 要跨線程維護正確的可見性,只要在幾個線程之間共享非 final 變量,就必須使用 synchronized(或 volatile)以確保一個線程可以看見另一個線程做的更改。 為了在線程之間進行可靠的通信,也為了互斥訪問,同步是必須的。這歸因於java語言規范的內存 模型,它規定了:一個線程所做的變化何時以及如何變成對其它線程可見。 因為多線程將異步行為引進程序,所以在需要同步時,必須有一種方法強制進行。例如:如果2個線 程想要通信並且要共享一個復雜的數據結構,如鏈表,此時需要確保它們互不沖突,也就是必須阻 止B線程在A線程讀數據的過程中向鏈表里面寫數據(A獲得了鎖,B必須等A釋放了該鎖)。 為了達到這個目的,java在一個舊的的進程同步模型——監控器(Monitor)的基礎上實現了一個巧 妙的方案:監控器是一個控制機制,可以認為是一個很小的、只能容納一個線程的盒子,一旦一個 線程進入監控器,其它的線程必須等待,直到那個線程退出監控為止。通過這種方式,一個監控器 可以保證共享資源在同一時刻只可被一個線程使用。這種方式稱之為同步。(一旦一個線程進入一 個實例的任何同步方法,別的線程將不能進入該同一實例的其它同步方法,但是該實例的非同步方 法仍然能夠被調用)。 錯誤的理解:同步嘛,就是幾個線程可以同時進行訪問。 同步和多線程關系:沒多線程環境就不需要同步;有多線程環境也不一定需要同步。 鎖提供了兩種主要特性:互斥(mutual exclusion)和可見性(visibility)。 互斥即一次只允許一個線程持有某個特定的鎖,因此可使用該特性實現對共享數據的協調訪問協議 ,這樣,一次就只有一個線程能夠使用該共享數據。 可見性要更加復雜一些,它必須確保釋放鎖之前對共享數據做出的更改對於隨后獲得該鎖的另一個 線程是可見的 —— 如果沒有同步機制提供的這種可見性保證,線程看到的共享變量可能是修改前 的值或不一致的值,這將引發許多嚴重問題 小結:為了防止多個線程並發對同一數據的修改,所以需要同步,否則會造成數據不一致(就是所 謂的:線程安全。如java集合框架中Hashtable和Vector是線程安全的。我們的大部分程序都不是線 程安全的,因為沒有進行同步,而且我們沒有必要,因為大部分情況根本沒有多線程環境)。 2、 什么叫原子的(原子操作)? Java原子操作是指:不會被打斷地的操作。(就是做到互斥和可見性?!) 那難道原子操作就可以真的達到線程安全同步效果了嗎?實際上有一些原子操作不一定是線程安全 的。 那么,原子操作在什么情況下不是線程安全的呢?也許是這個原因導致的:java線程允許線程在自 己的內存區保存變量的副本。允許線程使用本地的私有拷貝進行工作而非每次都使用主存的值是為 了提高性能(本人愚見:雖然原子操作是線程安全的,可各線程在得到變量(讀操作)后,就是各 自玩弄自己的副本了,更新操作(寫操作)因未寫入主存中,導致其它線程不可見)。 那該如何解決呢?因此需要通過java同步機制。 在java中,32位或者更少位數的賦值是原子的。在一個32位的硬件平台上,除了double和long 型的其它原始類型通常都是使用32位進行表示,而double和long通常使用64位表示。另外,對象引 用使用本機指針實現,通常也是32位的。對這些32位的類型的操作是原子的。 這些原始類型通常使用32位或者64位表示,這又引入了另一個小小的神話:原始類型的大小是 由語言保證的。這是不對的。java語言保證的是原始類型的表數范圍而非JVM中的存儲大小。因此, int型總是有相同的表數范圍。在一個JVM上可能使用32位實現,而在另一個JVM上可能是64位的。在 此再次強調:在所有平台上被保證的是表數范圍,32位以及更小的值的操作是原子的。 3、 不要搞混了:同步、異步 舉個例子:普通B/S模式(同步)AJAX技術(異步) 同步:提交請求->等待服務器處理->處理完返回這個期間客戶端瀏覽器不能干任何事 異步:請求通過事件觸發->服務器處理(這是瀏覽器仍然可以作其他事情)->處理完畢 可見,彼“同步”非此“同步”——我們說的java中的那個共享數據同步(synchronized) 一個同步的對象是指行為(動作),一個是同步的對象是指物質(共享數據)。 4、 Java同步機制有4種實現方式:(部分引用網上資源) ① ThreadLocal ② synchronized( ) ③ wait() 與 notify() ④ volatile 目的:都是為了解決多線程中的對同一變量的訪問沖突 ThreadLocal ThreadLocal 保證不同線程擁有不同實例,相同線程一定擁有相同的實例,即為每一個使用該 變量的線程提供一個該變量值的副本,每一個線程都可以獨立改變自己的副本,而不是與其它線程 的副本沖突。 優勢:提供了線程安全的共享對象 與其它同步機制的區別:同步機制是為了同步多個線程對相同資源的並發訪問,是為了多個線程之 間進行通信;而 ThreadLocal 是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源 ,這樣當然不需要多個線程進行同步了。 volatile volatile 修飾的成員變量在每次被線程訪問時,都強迫從共享內存中重讀該成員變量的值。 而且,當成員變量發生變化時,強迫線程將變化值回寫到共享內存。 優勢:這樣在任何時刻,兩個不同的線程總是看到某個成員變量的同一個值。 緣由:Java 語言規范中指出,為了獲得最佳速度,允許線程保存共享成員變量的私有拷貝,而 且只當線程進入或者離開同步代碼塊時才與共享成員變量的原始值對比。這樣當多個線程同時與某 個對象交互時,就必須要注意到要讓線程及時的得到共享成員變量的變化。而 volatile 關鍵字就 是提示 VM :對於這個成員變量不能保存它的私有拷貝,而應直接與共享成員變量交互。 使用技巧:在兩個或者更多的線程訪問的成員變量上使用 volatile 。當要訪問的變量已在 synchronized 代碼塊中,或者為常量時,不必使用。 線程為了提高效率,將某成員變量(如A)拷貝了一份(如B),線程中對A的訪問其實訪問的 是B。只在某些動作時才進行A和B的同步,因此存在A和B不一致的情況。volatile就是用來避免這種 情況的。 volatile告訴jvm,它所修飾的變量不保留拷貝,直接訪問主內存中的(讀操作多時使用 較好;線程間需要通信,本條做不到) Volatile 變量具有 synchronized 的可見性特性,但是不具備原子特性。這就是說線程能夠自 動發現 volatile 變量的最新值。Volatile 變量可用於提供線程安全,但是只能應用於非常有限的 一組用例:多個變量之間或者某個變量的當前值與修改后值之間沒有約束。 您只能在有限的一些情形下使用 volatile 變量替代鎖。要使 volatile 變量提供理 想的線程安全,必須同時滿足下面兩個條件: 對變量的寫操作不依賴於當前值;該變量沒有包含在具有其他變量的不變式中。 sleep() vs wait() sleep是線程類(Thread)的方法,導致此線程暫停執行指定時間,把執行機會給其他線程,但是監 控狀態依然保持,到時后會自動恢復。調用sleep不會釋放對象鎖。 wait是Object類的方法,對此對象調用wait方法導致本線程放棄對象鎖,進入等待此對象的等待鎖 定池,只有針對此對象發出notify方法(或notifyAll)后本線程才進入對象鎖定池准備獲得對象鎖 進入運行狀態。 (如果變量被聲明為volatile,在每次訪問時都會和主存一致;如果變量在同步方法或者同步塊中 被訪問,當在方法或者塊的入口處獲得鎖以及方法或者塊退出時釋放鎖時變量被同步。) 四、例子: Demo1: package test.thread; class SynTest{ //非同步 static void method(Thread thread){ System.out.println("begin "+thread.getName()); try{ Thread.sleep(2000); }catch(Exception ex){ ex.printStackTrace(); } System.out.println("end "+thread.getName()); } //同步方式一:同步方法 synchronized static void method1(Thread thread){//這個方法是同步的方法,每次只有一 個線程可以進來 System.out.println("begin "+thread.getName()); try{ Thread.sleep(2000); }catch(Exception ex){ ex.printStackTrace(); } System.out.println("end "+thread.getName()); } //同步方式二:同步代碼塊 static void method2(Thread thread){ synchronized(SynTest.class) { System.out.println("begin "+thread.getName()); try{ Thread.sleep(2000); }catch(Exception ex){ ex.printStackTrace(); } System.out.println("end "+thread.getName()); } } //同步方式三:使用同步對象鎖 private static Object _lock1=new Object(); private static byte _lock2[]={};//據說,此鎖更可提高性能。源於:鎖的對象越小越好 static void method3(Thread thread){ synchronized(_lock1) { System.out.println("begin "+thread.getName()); try{ Thread.sleep(2000); }catch(Exception ex){ ex.printStackTrace(); } System.out.println("end "+thread.getName()); } } public static void main(String[] args){ //啟動3個線程,這里用了匿名類 for(int i=0;i<3;i++){ new Thread(){ public void run(){ method(this); //method1(this); //method2(this); //method3(this); } }.start(); } } } Demo2: package test.thread; import com.util.LogUtil; public class SynTest2 { public static void main(String[] args){ Callme target=new Callme(); Caller ob1=new Caller(target,"Hello"); Caller ob2=new Caller(target,"Synchronized"); Caller ob3=new Caller(target,"World"); } } class Callme{ synchronized void test(){ LogUtil.log("測試是否是:一旦一個線程進入一個實例的任何同步方法,別的線程將不能 進入該同一實例的其它同步方法,但是該實例的非同步方法仍然能夠被調用"); } void nonsynCall(String msg){ LogUtil.log("["+msg); LogUtil.log("]"); } synchronized void synCall(String msg){ LogUtil.logPrint("["+msg); LogUtil.log("]"); } } class Caller implements Runnable{ String msg; Callme target; Thread t; Caller(Callme target,String msg){ this.target=target; this.msg=msg; t=new Thread(this); t.start(); } public void run() { // TODO Auto-generated method stub //target.nonsynCall(msg); target.synCall(msg); target.test(); } }