volatile、synchronized、(原子、可见、有序)、先行发生原则
Java代碼---編譯--->Java字節碼---類加載器加載到JVM中--->匯編指令(在CPU上執行)
Java中的并發機制依賴于JVM的實現和CPU指令
1.volatile
輕量級synchronized,使用成本低,不會引起上下文切換和調度。
1.1 主要使用的場合
是在多個線程之間感知實例變量的修改,并且可以獲得最新值使用,當線程想要訪問volatile修飾的變量時強制從公共堆棧中進行讀取。
volatile變量在線程內存中被修改之后要立即同步回主內存中,以保證其他線程使用該volatile關鍵字修飾的變量時獲取到的是最新的變量值。
volatile可以保證每次線程從主內存中刷新到最新的變量值,但是不能保證變量值被加載到線程內存之后對該變量做的修改操作是原子性的。也就是說,volatile關鍵字保證的是變量在不同線程之間的可見性,但是無法保證原子性,對于多個線程訪問同一個實例變量還是需要加鎖同步。
1.2?volatile變量的兩個特性
1、保證可見性:此變量對于所有線程的可見性,指的是當一個線程修改了這個變量的值,新值對于其他線程來說是立即可見的。
2、禁止指令重排序優化(內存屏障,也會犧牲掉一些性能)
1.2.1?指令重排優化
在保證可以得到程序正確執行結果的前提下,CPU允許將多條指令不按程序規定的順序分開發送到各個相應電路單元處理。
1.3?某個變量定義為volatile的應用場景
① 運算結果不依賴于變量的當前值或者能夠確保只有一個線程修改這個變量的值;
② 變量不需要與其他的狀態變量共同參與不變約束。
1.4?Java內存模型對volatile變量定義的特殊規則
① 在工作內存中每次使用變量前都需要從主內存中刷新最新值;
② 每次修改變量的值之后都必須立刻同步到主內存中;
③ 要求volatile修飾的變量不會被指令重新排序。
1.5 不支持原子性,非線程安全
對一個volatile修飾的變量的讀寫操作不是原子性的。
因為如果在第一個線程加載某個volatile修飾的變量值到工作內存之后有其他線程修改了這個變量值,那么第一個線程是感知不到這個值的變化的。這個時候就會出現線程安全的問題,所以為了保證線程安全問題還是需要synchronized關鍵字。
1.6 volatile變量的使用
并不建議過多地依賴volatile變量。如果在代碼中過多地依賴volatile變量來控制狀態可見性,通常比使用鎖的代碼更脆弱,也更難以理解。
僅當volatile變量能簡化代碼的實現以及對同步策略的驗證,才應使用volatile變量。
如果在驗證正確性時需要對可見性進行復雜的判斷,那么就不要使用volatile變量。
volatile變量的正確使用方式包括:
- 確保它們自身狀態的可見性;
- 確保它們所引用的對象的狀態的可見性;
- 標識一些重要的程序生命周期事件的發生(比如初始化或關閉)。
2.synchronized
synchronized關鍵字保證在同一時刻,只有一個線程可以執行某個對象內某一個方法或某一段代碼塊。
重量級鎖。包含兩個特征:互斥性和可見性。
synchronized可以解決一個線程看到對象處于不一致的狀態,可以保證進入同步方法或者同步代碼塊的每個線程都可以看到由同一個鎖保護之前所有的修改效果。
實現同步的基礎:Java中每個對象都可作為鎖。
| 方法 | static方法 | 鎖的是當前類的class對象,該類的所有對象訪問這個static方法都被阻塞。 |
| 非static方法 | 鎖的是當前實例對象,必須持有該對象鎖才能訪問對象內的同步方法。 | |
| 代碼塊 | this對象 | 與鎖非static方法一樣,都是給對象上鎖。 |
| 非this對象x | 給對象x上鎖,想訪問這個代碼段必須持有這個對象x的鎖才可以。 | |
| class對象 | 與給static方法加同步關鍵字一樣,鎖的都是class對象。 |
注意:class鎖和對象鎖不是同一種鎖。
- 持有class鎖則可以訪問同步的static方法和鎖定class對象的代碼段;
- 持有對象鎖則可以訪問同步的非static方法和鎖定this對象的代碼段。
- 只有多個線程持有同一個對象的鎖時,訪問該對象內的同步方法才會被阻塞,如果持有的不是同一個對象的鎖則異步執行。
2.1 synchronized關鍵字可以用于同步方法和同步代碼塊。
同步方法又可分為同步static方法和非static方法
2.1.1 如果是給static方法加上synchronized關鍵字
則說明同步的是當前類的.class類,那么后面所有對這個static方法的訪問都會被阻塞,但是此時可以訪問其他沒有加synchronized關鍵字的方法或者是加了synchronized關鍵字的非static方法;
2.1.2? 如果給非static方法加上synchronized關鍵字
則同步的是當前對象,這樣的話其他想訪問同一個對象下的synchronized同步方法就會被阻塞,但是不影響訪問synchronized同步的static方法,因為synchronized非static方法是某個對象實例加鎖,而synchronized static方法是給.class對象加鎖,
但是class鎖是對類的所有對象都有效,也就是說如果現在有個static方法加上了synchronized關鍵字,則這個類的所有對象都會對這個方法進行同步操作。
2.2 synchronized同步代碼塊
synchronized同步代碼塊的時候分為synchronized(this 對象)、synchronized(非this 對象)、synchronized(class對象)。
2.2.1 synchronized(this 對象)
synchronized(this 對象)同步的也是當前對象,而synchronized(非this 對象)則是對某個非this對象進行同步即鎖定。
2.2.2 synchronized(非this 對象)
synchronized(非this 對象)同步代碼塊的方法在進行同步操作時,對象監視器必須是同一個對象。如果不是同一個對象監視器,運行的結果就是異步調用了。
2.2.3 synchronized(class對象)
synchronized(class對象)與給static方法加上synchronized關鍵字是一樣的。
2.4 使用synchronized需要注意的地方
1、synchronized鎖可重入:持有某個對象的鎖可繼續訪問需要持有該對象鎖才可訪問的方法和代碼段;
2、可重入鎖也支持在父子類繼承的環境中:調用子類中同步的方法時也可以訪問父類中需要對象鎖的方法和代碼段;
3、 同步方法內出現異常時將會自動釋放持有的對象鎖;
4、同步不能繼承,所以還需要在子類中需要同步的方法上加同步關鍵字。
2.5 Monitor對象
JVM基于進入和退出Monitor對象來實現方法同步和代碼塊同步
2.5.1 代碼塊同步
monitorenter指令:在編譯后插入到同步代碼塊的開始位置;
monitorexit指令:下入到方法結束出和異常處。
Java保證每個1后必有對應的2。任何一個對象都有一個monitor與之關聯。
2.5.2方法同步
另一種方法,也用這兩個指令。
2.6
synchronized用的鎖是存在Java對象頭中的。
若對象是數組,則三個字寬:hashcode/鎖/分代年齡、指針、數組長度
若對象不是數組,則只用存前兩個。
3.volatile和synchronized的區別
| volatile | synchronized |
| 線程同步的輕量級實現 | ? |
| 只能用于同步變量 | 可以用于修飾方法和代碼塊 |
| 多線程訪問volatile不會發生阻塞 | 會發生阻塞 |
| 可保證數據的可見性,但是不能保證數據的一致性(原子性) | 可以保證原子性,也可以間接保證可見性,因為它會將私有內存和公共內存中的數據做同步。 |
| 解決的是多線程之間的可見性 | 解決的是多個線程之間訪問資源的同步性 |
4.并發編程中的三個概念(原子、可見、有序)
Java內存模型是圍繞著在并發過程中如何處理原子性、可見性和有序性三個特征建立的
4.1原子性
一個操作要么全部執行完畢 ,要么根本就不執行。
Java內存模型直接保證的原子性變量操作有read、load、assign、use、store和write。
4.2可見性
多個線程訪問同一個變量時一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
Java語言中的volatile、synchronized和final三個關鍵字都可保證操作時變量的可見性。
4.3有序性
即程序執行的順序按照代碼的先后順序執行。
Java語言提供了volatile和synchronized兩個關鍵字來保證線程之間操作的有序性。
總之,要想讓并發程序正確地執行,必須要保證原子性、可見性以及有序性。只要有一個沒有被保證,就有可能會導致程序運行不正確。
5.先行發生原則
先行發生是Java內存模型中定義的兩項操作之間的偏序關系:如果說操作A先行發生于操作B,那么操作A產生的影響將會被操作B觀察到。“影響”包括修改了內存中共享變量的值、發送了消息、修改了變量等。
5.1 Java內存模型中定義的一些“天然的”先行發生關系
① 程序次序原則(控制流中的先后順序)
② 管程鎖定原則(同一個鎖的unlock發生在下一次lock)
③ volatile變量規則(禁止指令重排優化)
④ 線程啟動原則(線程的start操作優先于線程內部的其他所有操作)
④ 線程終止規則(線程內部的所有操作優先于線程終止操作)
⑥ 線程中斷原則(對線程interrupt()方法的調用先行發生于被中斷檢測代碼檢測到中斷的發生)
⑦ 對象終結原則(對象初始化操作先行于finalize操作的發生)
⑧ 傳遞性(A先行發生于B,B先行發生于C,則A先行發生于C)。
時間先后順序與先行發生原則之間基本沒有太大關系,衡量并發問題的時候不要受到時間上先后發生的干擾,一切以先行發生原則為準。
總結
以上是生活随笔為你收集整理的volatile、synchronized、(原子、可见、有序)、先行发生原则的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hashtable源码分析
- 下一篇: 对象头、锁的四种状态、Java和处理器实