Posted on 2010-11-18 14:42
楊羅羅 閱讀(1089)
評(píng)論(0) 編輯 收藏 所屬分類:
java.thread
JMM規(guī)范:
The rules for happens-before are:
Program order rule. Each action in a thread happens-before every action in that thread that comes later in the program order.
Monitor lock rule. An unlock on a monitor lock happens-before every subsequent lock on that same monitor lock.
Volatile variable rule. A write to a volatile field happens-before every subsequent read of that same field.
Thread start rule. A call to Thread.start on a thread happens-before every action in the started thread.
Thread termination rule. Any action in a thread happens-before any other thread detects that thread has terminated, either by successfully return from Thread.join or by Thread.isAlive returning false.
Interruption rule. A thread calling interrupt on another thread happens-before the interrupted thread detects the interrupt (either by having InterruptedException tHRown, or invoking isInterrupted or interrupted).
Finalizer rule. The end of a constructor for an object happens-before the start of the finalizer for that object.
Transitivity. If A happens-before B, and B happens-before C, then A happens-before C.
appens-before完整規(guī)則:
(1)同一個(gè)線程中的每個(gè)Action都happens-before于出現(xiàn)在其后的任何一個(gè)Action。
(2)對(duì)一個(gè)監(jiān)視器的解鎖happens-before于每一個(gè)后續(xù)對(duì)同一個(gè)監(jiān)視器的加鎖。
(3)對(duì)volatile字段的寫(xiě)入操作happens-before于每一個(gè)后續(xù)的同一個(gè)字段的讀操作。
(4)Thread.start()的調(diào)用會(huì)happens-before于啟動(dòng)線程里面的動(dòng)作。
(5)Thread中的所有動(dòng)作都happens-before于其他線程檢查到此線程結(jié)束或者Thread.join()中返回或者Thread.isAlive()==false。
(6)一個(gè)線程A調(diào)用另一個(gè)另一個(gè)線程B的interrupt()都happens-before于線程A發(fā)現(xiàn)B被A中斷(B拋出異常或者A檢測(cè)到B的isInterrupted()或者interrupted())。
(7)一個(gè)對(duì)象構(gòu)造函數(shù)的結(jié)束happens-before與該對(duì)象的finalizer的開(kāi)始
(8)如果A動(dòng)作happens-before于B動(dòng)作,而B(niǎo)動(dòng)作happens-before與C動(dòng)作,那么A動(dòng)作happens-before于C動(dòng)作。
----------------------------
什么是happens-before?
happens-before就是“什么什么一定在什么什么之前運(yùn)行”,也就是保證順序性。
因?yàn)镃PU是可以不按我們寫(xiě)代碼的順序執(zhí)行內(nèi)存的存取過(guò)程的,也就是指令會(huì)亂序或并行運(yùn)行,
只有上面的happens-before所規(guī)定的情況下,才保證順序性。
如:
- public class Test {
-
- private int a = 0;
-
- private long b = 0;
-
- public void set() {
- a = 1;
- b = -1;
- }
-
- public void check() {
- if (! ((b == 0) || (b == -1 && a == 1))
- throw new Exception("check Error!");
- }
- }
對(duì)于set()方法的執(zhí)行:
1. 編譯器可以重新安排語(yǔ)句的執(zhí)行順序,這樣b就可以在a之前賦值。如果方法是內(nèi)嵌的(inline),編譯器還可以把其它語(yǔ)句重新排序。
2. 處理器可以改變這些語(yǔ)句的機(jī)器指令的執(zhí)行順序,甚到同時(shí)執(zhí)行這些語(yǔ)句。
3. 存儲(chǔ)系統(tǒng)(由于被緩存控制單元控制)也可以重新安排對(duì)應(yīng)存儲(chǔ)單元的寫(xiě)操作順序,這些寫(xiě)操作可能與其他計(jì)算和存儲(chǔ)操作同時(shí)發(fā)生。
4. 編譯器,處理器和存儲(chǔ)系統(tǒng)都可以把這兩條語(yǔ)句的機(jī)器指令交叉執(zhí)行。
例如:在一臺(tái)32位的機(jī)器上,可以先寫(xiě)b的高位,然后寫(xiě)a,最后寫(xiě)b的低位,(注:b為long類型,在32位的機(jī)器上分高低位存儲(chǔ))
5. 編譯器,處理器和存儲(chǔ)系統(tǒng)都可以使對(duì)應(yīng)于變量的存儲(chǔ)單元一直保留著原來(lái)的值,
以某種方式維護(hù)相應(yīng)的值(例如,在CPU的寄存器中)以保證代碼正常運(yùn)行,直到下一個(gè)check調(diào)用才更新。
...
在單線程(或同步)的情況下,上面的check()永遠(yuǎn)不會(huì)報(bào)錯(cuò),
但非同步多線程運(yùn)行時(shí)卻很有可能。
并且,多個(gè)CPU之間的緩存也不保證實(shí)時(shí)同步,
也就是說(shuō)你剛給一個(gè)變量賦值,另一個(gè)線程立即獲取它的值,可能拿到的卻是舊值(或null),
因?yàn)閮蓚€(gè)線程在不同的CPU執(zhí)行,它們看到的緩存值不一樣,
只有在synchronized或volatile或final的性況下才能保證正確性,
很多人用synchronized時(shí)只記得有l(wèi)ock的功能,而忘記了線程間的可見(jiàn)性問(wèn)題。
如:
- public class Test {
-
- private int n;
-
- public void set(int n) {
- this.n = n;
- }
-
- public void check() {
- if (n != n)
- throw new Exception("check Error!");
- }
- }
check()中的 n != n 好像永遠(yuǎn)不會(huì)成立,因?yàn)樗麄冎赶蛲粋€(gè)值,但非同步時(shí)卻很有可能發(fā)生。
另外,JMM不保證創(chuàng)建過(guò)程的原子性,讀寫(xiě)并發(fā)時(shí),可能看到不完整的對(duì)象,
這也是為什么單例模式中著名的"雙重檢查成例"方法,在Java中行不通。(但.Net的內(nèi)存模型保證這一點(diǎn))
當(dāng)然,在Java中單例的延遲加載可以用另一種方案實(shí)現(xiàn)(方案四):
方案一:非延遲加載單例類
- public class Singleton {
-
- private Singleton(){}
-
- private static final Singleton instance = new Singleton();
-
- public static Singleton getInstance() {
- return instance;
- }
- }
方案二:簡(jiǎn)單的同步延遲加載
- public class Singleton {
-
- private static Singleton instance = null;
-
- public static synchronized Singleton getInstance() {
- if (instance == null)
- instance = new Singleton();
- return instance;
- }
-
- }
方案三:雙重檢查成例延遲加載
目的是避開(kāi)過(guò)多的同步,
但在Java中行不通,因?yàn)橥綁K外面的if (instance == null)可能看到已存在,但不完整的實(shí)例。
JDK5.0以后版本若instance為volatile則可行
- public class Singleton {
-
- private static Singleton instance = null;
-
- public static Singleton getInstance() {
- if (instance == null) {
- synchronized (Singleton.class) {
- if (instance == null) {
- instance = new Singleton();
- }
- }
- }
- return instance;
- }
-
- }
方案四:類加載器延遲加載
- public class Singleton {
-
- private static class Holder {
- static final Singleton instance = new Singleton();
- }
-
- public static Singleton getInstance() {
- return Holder.instance;
- }
-
- }