Java同步與異步(轉載)

Java同步與異步java

1、關鍵字:

thread(線程)、thread-safe(線程安全)、intercurrent(併發的)

synchronized(同步的)、asynchronized(異步的)、

volatile(易變的)、atomic(原子的)、share(共享)

2、總結背景:

一次讀寫共享文件編寫,嚯,好傢伙,居然揪出這些零碎而又是一路的知識點。因而乎,Google和翻閱了《Java參考大全》、《Effective Java Second Edition》,特此總結一下供往後工做學習參考。

3、概念:

一、 何時必須同步?什麼叫同步?如何同步?

       要跨線程維護正確的可見性,只要在幾個線程之間共享非 final 變量,就必須使用 synchronized(或 volatile)以確保一個線程能夠看見另外一個線程作的更改。

爲了在線程之間進行可靠的通訊,也爲了互斥訪問,同步是必須的。這歸因於java語言規範的內存模型,它規定了:一個線程所作的變化什麼時候以及如何變成對其它線程可見。

由於多線程將異步行爲引進程序,因此在須要同步時,必須有一種方法強制進行。例如:若是2個線程想要通訊而且要共享一個複雜的數據結構,如鏈表,此時須要確保它們互不衝突,也就是必須阻止B線程在A線程讀數據的過程當中向鏈表裏面寫數據(A得到了鎖,B必須等A釋放了該鎖)。

爲了達到這個目的,java在一箇舊的的進程同步模型——監控器(Monitor)的基礎上實現了一個巧妙的方案:監控器是一個控制機制,能夠認爲是一個很小的、只能容納一個線程的盒子,一旦一個線程進入監控器,其它的線程必須等待,直到那個線程退出監控爲止。經過這種方式,一個監控器能夠保證共享資源在同一時刻只可被一個線程使用。這種方式稱之爲同步。(一旦一個線程進入一個實例的任何同步方法,別的線程將不能進入該同一實例的其它同步方法,可是該實例的非同步方法仍然可以被調用)。

錯誤的理解:同步嘛,就是幾個線程能夠同時進行訪問。

同步和多線程關係:沒多線程環境就不須要同步;有多線程環境也不必定須要同步。

鎖提供了兩種主要特性:互斥(mutual exclusion) 和可見性(visibility)。

互斥即一次只容許一個線程持有某個特定的鎖,所以可以使用該特性實現對共享數據的協調訪問協議,這樣,一次就只有一個線程可以使用該共享數據。

可見性要更加複雜一些,它必須確保釋放鎖以前對共享數據作出的更改對於隨後得到該鎖的另外一個線程是可見的 —— 若是沒有同步機制提供的這種可見性保證,線程看到的共享變量多是修改前的值或不一致的值,這將引起許多嚴重問題

小結:爲了防止多個線程併發對同一數據的修改,因此須要同步,不然會形成數據不一致(就是所謂的:線程安全。如java集合框架中Hashtable和Vector是線程安全的。咱們的大部分程序都不是線程安全的,由於沒有進行同步,並且咱們沒有必要,由於大部分狀況根本沒有多線程環境)。



二、 什麼叫原子的(原子操做)?

     Java原子操做是指:不會被打斷地的操做。(就是作到互斥 和可見性?!)

那難道原子操做就能夠真的達到線程安全同步效果了嗎?實際上有一些原子操做不必定是線程安全的。

那麼,原子操做在什麼狀況下不是線程安全的呢?也許是這個緣由致使的:java線程容許線程在本身的內存區保存變量的副本。容許線程使用本地的私有拷貝進行工做而非每次都使用主存的值是爲了提升性能(本人愚見:雖然原子操做是線程安全的,可各線程在獲得變量(讀操做)後,就是各自玩弄本身的副本了,更新操做(寫操做)因未寫入主存中,致使其它線程不可見)。

那該如何解決呢?所以須要經過java同步機制。

     在java中,32位或者更少位數的賦值是原子的。在一個32位的硬件平臺上,除了double和long型的其它原始類型一般都是使用32位進行表示,而double和long一般使用64位表示。另外,對象引用使用本機指針實現,一般也是32位的。對這些32位的類型的操做是原子的。

     這些原始類型一般使用32位或者64位表示,這又引入了另外一個小小的神話:原始類型的大小是由語言保證的。這是不對的。java語言保證的是原始類型的表數範圍而非JVM中的存儲大小。所以,int型老是有相同的表數範圍。在一個JVM上可能使用32位實現,而在另外一個JVM上多是64位的。在此再次強調:在全部平臺上被保證的是表數範圍,32位以及更小的值的操做是原子的。

    

三、 不要搞混了:同步、異步

舉個例子:普通B/S模式(同步)AJAX技術(異步)

同步:提交請求->等待服務器處理->處理完返回 這個期間客戶端瀏覽器不能幹任何事

異步:請求經過事件觸發->服務器處理(這是瀏覽器仍然能夠做其餘事情)->處理完畢

可見,彼「同步」非此「同步」——咱們說的java中的那個共享數據同步(synchronized)

一個同步的對象是指行爲(動做),一個是同步的對象是指物質(共享數據)。



四、 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,在每次訪問時都會和主存一致;若是變量在同步方法或者同步塊中被訪問,當在方法或者塊的入口處得到鎖以及方法或者塊退出時釋放鎖時變量被同步。)瀏覽器

相關文章
相關標籤/搜索