1、鎖提供了兩種主要特性:互斥(mutual exclusion) 和可見性(visibility)。
互斥即一次只允許一個線程持有某個特定的鎖,因此可使用該特性實現對共享數據的協調訪問協議,這樣,一次就只有一個線程能夠使用該共享數據。
可見性要更加複雜一些,它必須確保釋放鎖之前對共享數據做出的更改對於隨後獲得該鎖的另一個線程是可見的 —— 如果沒有同步機制提供的這種可見性保證,線程看到的共享 變量可能是修改前的值或不一致的值,這將引發許多嚴重問題。(競態條件)
2、在Java中,爲了保證多線程讀寫數據時保證數據的一致性,可以採用兩種方式:
同步:如用synchronized關鍵字,或者使用鎖對象
使用volatile關鍵字:用一句話概括volatile,它能夠使變量在值發生改變時能儘快地讓其他線程知道。
3、volatile詳解
當一個變量定義爲volatile後,它將具備兩種特性:1. 可見性,2. 禁止指令重排序。
首先,我們要意識到有這樣的現象:編譯器爲了加快程序運行速度,對一些變量的寫操作會現在寄存器或CPU緩存上進行,最後寫入內存。而在這個過程中,變量的新值對其它線程是不可見的。
可見性:當對volatile標記的變量進行修改時,會將其它緩存中存儲的修改前的變量清除,然後重新讀取。這裏從哪讀尚未明確,一般來說應該是先在進行修改的緩存A中修改爲新值,然後通知其它緩存清除掉此變量,當其它緩存B中的線程讀取此變量時,會向總線發送消息,這是存儲新值的緩存A獲取到消息,將新值傳給B,最後將新值寫入內存。
volatile的作用是被其修飾的變量每次刷新時,都會刷新上述步驟。
4、volatile與synchronized
- volatile本質是告訴JVM當前變量在寄存器中的值是不確定的,需要從主存中讀取。synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其它線程被阻塞。
- volatile僅能使用在變量級別,synchronized則可以使用在變量、方法。
- volatile僅能實現變量修改的可見性,而synchronized則可以保證變量修改的可見性和原子性。《Java編程思想》上說,定義long或double時,如果使用volatile關鍵字(簡單的賦值與返回操作),就會獲得原子性。(常規狀態下,這兩個變量由於其長度,其操作不是原子的)
- volatile不會造成線程阻塞,synchronized會造成線程阻塞。
- 使用volatile而不是synchronized的唯一安全情況是類中只有一個可變的域。
- 當一個域的值依賴於它之前的值時,volatile就無法工作了,如n=n+1,n++等。如果某個域的值受到其他域的值的限制,那麼volatile也無法工作,如Range類的lower和upper邊界,必須遵循lower<=upper的限制。