Posted on 2010-11-18 14:42
楊羅羅 閱讀(1090)
評論(0) 編輯 收藏 所屬分類:
java.thread
JMM規范:
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完整規則:
(1)同一個線程中的每個Action都happens-before于出現在其后的任何一個Action。
(2)對一個監視器的解鎖happens-before于每一個后續對同一個監視器的加鎖。
(3)對volatile字段的寫入操作happens-before于每一個后續的同一個字段的讀操作。
(4)Thread.start()的調用會happens-before于啟動線程里面的動作。
(5)Thread中的所有動作都happens-before于其他線程檢查到此線程結束或者Thread.join()中返回或者Thread.isAlive()==false。
(6)一個線程A調用另一個另一個線程B的interrupt()都happens-before于線程A發現B被A中斷(B拋出異?;蛘逜檢測到B的isInterrupted()或者interrupted())。
(7)一個對象構造函數的結束happens-before與該對象的finalizer的開始
(8)如果A動作happens-before于B動作,而B動作happens-before與C動作,那么A動作happens-before于C動作。
----------------------------
什么是happens-before?
happens-before就是“什么什么一定在什么什么之前運行”,也就是保證順序性。
因為CPU是可以不按我們寫代碼的順序執行內存的存取過程的,也就是指令會亂序或并行運行,
只有上面的happens-before所規定的情況下,才保證順序性。
如:
- 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!");
- }
- }
對于set()方法的執行:
1. 編譯器可以重新安排語句的執行順序,這樣b就可以在a之前賦值。如果方法是內嵌的(inline),編譯器還可以把其它語句重新排序。
2. 處理器可以改變這些語句的機器指令的執行順序,甚到同時執行這些語句。
3. 存儲系統(由于被緩存控制單元控制)也可以重新安排對應存儲單元的寫操作順序,這些寫操作可能與其他計算和存儲操作同時發生。
4. 編譯器,處理器和存儲系統都可以把這兩條語句的機器指令交叉執行。
例如:在一臺32位的機器上,可以先寫b的高位,然后寫a,最后寫b的低位,(注:b為long類型,在32位的機器上分高低位存儲)
5. 編譯器,處理器和存儲系統都可以使對應于變量的存儲單元一直保留著原來的值,
以某種方式維護相應的值(例如,在CPU的寄存器中)以保證代碼正常運行,直到下一個check調用才更新。
...
在單線程(或同步)的情況下,上面的check()永遠不會報錯,
但非同步多線程運行時卻很有可能。
并且,多個CPU之間的緩存也不保證實時同步,
也就是說你剛給一個變量賦值,另一個線程立即獲取它的值,可能拿到的卻是舊值(或null),
因為兩個線程在不同的CPU執行,它們看到的緩存值不一樣,
只有在synchronized或volatile或final的性況下才能保證正確性,
很多人用synchronized時只記得有lock的功能,而忘記了線程間的可見性問題。
如:
- 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 好像永遠不會成立,因為他們指向同一個值,但非同步時卻很有可能發生。
另外,JMM不保證創建過程的原子性,讀寫并發時,可能看到不完整的對象,
這也是為什么單例模式中著名的"雙重檢查成例"方法,在Java中行不通。(但.Net的內存模型保證這一點)
當然,在Java中單例的延遲加載可以用另一種方案實現(方案四):
方案一:非延遲加載單例類
- public class Singleton {
-
- private Singleton(){}
-
- private static final Singleton instance = new Singleton();
-
- public static Singleton getInstance() {
- return instance;
- }
- }
方案二:簡單的同步延遲加載
- public class Singleton {
-
- private static Singleton instance = null;
-
- public static synchronized Singleton getInstance() {
- if (instance == null)
- instance = new Singleton();
- return instance;
- }
-
- }
方案三:雙重檢查成例延遲加載
目的是避開過多的同步,
但在Java中行不通,因為同步塊外面的if (instance == null)可能看到已存在,但不完整的實例。
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;
- }
-
- }