日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Java多線程發(fā)展簡史

這篇文章,大部分內(nèi)容,是周五我做的一個關(guān)于如何進(jìn)行Java多線程編程的Knowledge Sharing的一個整理,我希望能對Java從第一個版本開始,在多線程編程方面的大事件和發(fā)展脈絡(luò)有一個描述,并且提及一些在多線程編程方面常見的問題。對于Java程序員來說,如果從歷史的角度去了解一門語言一個特性的演進(jìn),或許能有不同收獲。

成都網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)建站!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、小程序制作、集團(tuán)企業(yè)網(wǎng)站設(shè)計等服務(wù)項目。核心團(tuán)隊均擁有互聯(lián)網(wǎng)行業(yè)多年經(jīng)驗,服務(wù)眾多知名企業(yè)客戶;涵蓋的客戶類型包括:酒店設(shè)計等眾多領(lǐng)域,積累了大量豐富的經(jīng)驗,同時也獲得了客戶的一致表揚!

引言

首先問這樣一個問題,如果提到Java多線程編程,你會想到什么?

  • volatile、synchronized關(guān)鍵字?
  • 競爭和同步?
  • 鎖機(jī)制?
  • 線程安全問題?
  • 線程池和隊列?

好吧,請原諒我在這里賣的關(guān)子,其實這些都對,但是又不足夠全面,如果我們這樣來談?wù)揓ava多線程會不會全面一些:

  1. 模型:JMM(Java內(nèi)存模型)和JCM(Java并發(fā)模型)
  2. 使用:JDK中的并發(fā)包
  3. 實踐:怎樣寫線程安全的代碼
  4. 除錯:使用工具來分析并發(fā)問題
  5. ……

可是,這未免太死板了,不是么?

不如換一個思路,我們少談一些很容易查到的語法,不妨從歷史的角度看看Java在多線程編程方面是怎樣進(jìn)化的,這個過程中,它做了哪些正確的決定,犯了哪些錯誤,未來又會有怎樣的發(fā)展趨勢?

另外,還有一點要說是,我希望通過大量的實例代碼來說明這些事情。Linus說:“Talk is cheap, show me the code.”。下文涉及到的代碼我已經(jīng)上傳,可以在此打包下載。

誕生

Java的基因來自于1990年12月Sun公司的一個內(nèi)部項目,目標(biāo)設(shè)備正是家用電器,但是C++的可移植性和API的易用性都讓程序員反感。旨在解決這樣的問題,于是又了Java的前身Oak語言,但是知道1995年3月,它正式更名為Java,才算Java語言真正的誕生。

JDK 1.0

1996年1月的JDK1.0版本,從一開始就確立了Java最基礎(chǔ)的線程模型,并且,這樣的線程模型再后續(xù)的修修補(bǔ)補(bǔ)中,并未發(fā)生實質(zhì)性的變更,可以說是一個具有傳承性的良好設(shè)計。

搶占式和協(xié)作式是兩種常見的進(jìn)程/線程調(diào)度方式,操作系統(tǒng)非常適合使用搶占式方式來調(diào)度它的進(jìn)程,它給不同的進(jìn)程分配時間片,對于長期無響應(yīng)的進(jìn)程,它有能力剝奪它的資源,甚至將其強(qiáng)行停止(如果采用協(xié)作式的方式,需要進(jìn)程自覺、主動地釋放資源,也許就不知道需要等到什么時候了)。Java語言一開始就采用協(xié)作式的方式,并且在后面發(fā)展的過程中,逐步廢棄掉了粗暴的stop/resume/suspend這樣的方法,它們是違背協(xié)作式的不良設(shè)計,轉(zhuǎn)而采用wait/notify/sleep這樣的兩邊線程配合行動的方式。

一種線程間的通信方式是使用中斷:

 
 
 
 
  1. public class InterruptCheck extends Thread {  
  2.    
  3.     @Override 
  4.     public void run() {  
  5.         System.out.println("start");  
  6.         while (true)  
  7.             if (Thread.currentThread().isInterrupted())  
  8.                 break;  
  9.         System.out.println("while exit");  
  10.     }  
  11.    
  12.     public static void main(String[] args) {  
  13.         Thread thread = new InterruptCheck();  
  14.         thread.start();  
  15.         try {  
  16.             sleep(2000);  
  17.         } catch (InterruptedException e) {  
  18.         }  
  19.         thread.interrupt();  
  20.     }  

這是中斷的一種使用方式,看起來就像是一個標(biāo)志位,線程A設(shè)置這個標(biāo)志位,線程B時不時地檢查這個標(biāo)志位。另外還有一種使用中斷通信的方式,如下:

 
 
 
 
  1. public class InterruptWait extends Thread {  
  2.     public static Object lock = new Object();  
  3.    
  4.     @Override 
  5.     public void run() {  
  6.         System.out.println("start");  
  7.         synchronized (lock) {  
  8.             try {  
  9.                 lock.wait();  
  10.             } catch (InterruptedException e) {  
  11.                 System.out.println(Thread.currentThread().isInterrupted());  
  12.                 Thread.currentThread().interrupt(); // set interrupt flag again  
  13.                 System.out.println(Thread.currentThread().isInterrupted());  
  14.                 e.printStackTrace();  
  15.             }  
  16.         }  
  17.     }  
  18.    
  19.     public static void main(String[] args) {  
  20.         Thread thread = new InterruptWait();  
  21.         thread.start();  
  22.         try {  
  23.             sleep(2000);  
  24.         } catch (InterruptedException e) {  
  25.         }  
  26.         thread.interrupt();  
  27.     }  

在這種方式下,如果使用wait方法處于等待中的線程,被另一個線程使用中斷喚醒,于是拋出InterruptedException,同時,中斷標(biāo)志清除,這時候我們通常會在捕獲該異常的地方重新設(shè)置中斷,以便后續(xù)的邏輯通過檢查中斷狀態(tài)來了解該線程是如何結(jié)束的。

在比較穩(wěn)定的JDK 1.0.2版本中,已經(jīng)可以找到Thread和ThreadUsage這樣的類,這也是線程模型中最核心的兩個類。整個版本只包含了這樣幾個包:java.io、 java.util、java.net、java.awt和java.applet,所以說Java從一開始這個非常原始的版本就確立了一個持久的線程模型。

值得一提的是,在這個版本中,原子對象AtomicityXXX已經(jīng)設(shè)計好了,這里給出一個例子,說明i++這種操作時非原子的,而使用原子對象可以保證++操作的原子性:

 
 
 
 
  1. import java.util.concurrent.atomic.AtomicInteger;  
  2.    
  3. public class Atomicity {  
  4.    
  5.     private static volatile int nonAtomicCounter = 0;  
  6.     private static volatile AtomicInteger atomicCounter = new AtomicInteger(0);  
  7.     private static int times = 0;  
  8.    
  9.     public static void caculate() {  
  10.         times++;  
  11.         for (int i = 0; i < 1000; i++) {  
  12.             new Thread(new Runnable() {  
  13.                 @Override 
  14.                 public void run() {  
  15.                     nonAtomicCounter++;  
  16.                     atomicCounter.incrementAndGet();  
  17.                 }  
  18.             }).start();  
  19.         }  
  20.    
  21.         try {  
  22.             Thread.sleep(1000);  
  23.         } catch (InterruptedException e) {  
  24.         }  
  25.     }  
  26.    
  27.     public static void main(String[] args) {  
  28.         caculate();  
  29.         while (nonAtomicCounter == 1000) {  
  30.             nonAtomicCounter = 0;  
  31.             atomicCounter.set(0);  
  32.             caculate();  
  33.         }  
  34.    
  35.         System.out.println("Non-atomic counter: " + times + ":" 
  36.                 + nonAtomicCounter);  
  37.         System.out.println("Atomic counter: " + times + ":" + atomicCounter);  
  38.     }  

上面這個例子你也許需要跑幾次才能看到效果,使用非原子性的++操作,結(jié)果經(jīng)常小于1000。

對于鎖的使用,網(wǎng)上可以找到各種說明,但表述都不夠清晰。請看下面的代碼:

 
 
 
 
  1. public class Lock {  
  2.     private static Object o = new Object();  
  3.     static Lock lock = new Lock();  
  4.    
  5.     // lock on dynamic method  
  6.     public synchronized void dynamicMethod() {  
  7.         System.out.println("dynamic method");  
  8.         sleepSilently(2000);  
  9.     }  
  10.    
  11.     // lock on static method  
  12.     public static synchronized void staticMethod() {  
  13.         System.out.println("static method");  
  14.         sleepSilently(2000);  
  15.     }  
  16.    
  17.     // lock on this  
  18.     public void thisBlock() {  
  19.         synchronized (this) {  
  20.             System.out.println("this block");  
  21.             sleepSilently(2000);  
  22.         }  
  23.     }  
  24.    
  25.     // lock on an object  
  26.     public void objectBlock() {  
  27.         synchronized (o) {  
  28.             System.out.println("dynamic block");  
  29.             sleepSilently(2000);  
  30.         }  
  31.     }  
  32.    
  33.     // lock on the class  
  34.     public static void classBlock() {  
  35.         synchronized (Lock.class) {  
  36.             System.out.println("static block");  
  37.             sleepSilently(2000);  
  38.         }  
  39.     }  
  40.    
  41.     private static void sleepSilently(long millis) {  
  42.         try {  
  43.             Thread.sleep(millis);  
  44.         } catch (InterruptedException e) {  
  45.             e.printStackTrace();  
  46.         }  
  47.     }  
  48.    
  49.     public static void main(String[] args) {  
  50.    
  51.         // object lock test  
  52.         new Thread() {  
  53.             @Override 
  54.             public void run() {  
  55.                 lock.dynamicMethod();  
  56.             }  
  57.         }.start();  
  58.         new Thread() {  
  59.             @Override 
  60.             public void run() {  
  61.                 lock.thisBlock();  
  62.             }  
  63.         }.start();  
  64.         new Thread() {  
  65.             @Override 
  66.             public void run() {  
  67.                 lock.objectBlock();  
  68.             }  
  69.         }.start();  
  70.    
  71.         sleepSilently(3000);  
  72.         System.out.println();  
  73.    
  74.         // class lock test  
  75.         new Thread() {  
  76.             @Override 
  77.             public void run() {  
  78.                 lock.staticMethod();  
  79.             }  
  80.         }.start();  
  81.         new Thread() {  
  82.             @Override 
  83.             public void run() {  
  84.                 lock.classBlock();  
  85.             }  
  86.         }.start();  
  87.    
  88.     }  

上面的例子可以反映對一個鎖競爭的現(xiàn)象,結(jié)合上面的例子,理解下面這兩條,就可以很容易理解synchronized關(guān)鍵字的使用:

  • 非靜態(tài)方法使用synchronized修飾,相當(dāng)于synchronized(this)。
  • 靜態(tài)方法使用synchronized修飾,相當(dāng)于synchronized(Lock.class)。

#p#

JDK 1.2

1998年年底的JDK1.2版本正式把Java劃分為J2EE/J2SE/J2ME三個不同方向。在這個版本中,Java試圖用Swing修正在 AWT中犯的錯誤,例如使用了太多的同步。可惜的是,Java本身決定了AWT還是Swing性能和響應(yīng)都難以令人滿意,這也是Java桌面應(yīng)用難以比及其服務(wù)端應(yīng)用的一個原因,在IBM后來的SWT,也不足以令人滿意,JDK在這方面到JDK 1.2后似乎反省了自己,停下腳步了。值得注意的是,JDK高版本修復(fù)低版本問題的時候,通常遵循這樣的原則:

  1. 向下兼容。所以往往能看到很多重新設(shè)計的新增的包和類,還能看到deprecated的類和方法,但是它們并不能輕易被刪除。
  2. 嚴(yán)格遵循JLS(Java Language Specification),并把通過的新JSR(Java Specification Request)補(bǔ)充到JLS中,因此這個文檔本身也是向下兼容的,后面的版本只能進(jìn)一步說明和特性增強(qiáng),對于一些最初擴(kuò)展性比較差的設(shè)計,也會無能為力。這個在下文中關(guān)于ReentrantLock的介紹中也可以看到。

在這個版本中,正式廢除了這樣三個方法:stop()、suspend()和resume()。下面我就來介紹一下,為什么它們要被廢除:

 
 
 
 
  1. public class Stop extends Thread {  
  2.     @Override 
  3.     public void run() {  
  4.         try {  
  5.             while (true)  
  6.                 ;  
  7.         } catch (Throwable e) {  
  8.             e.printStackTrace();  
  9.         }  
  10.     }  
  11.    
  12.     public static void main(String[] args) {  
  13.         Thread thread = new Stop();  
  14.         thread.start();  
  15.    
  16.         try {  
  17.             sleep(1000);  
  18.         } catch (InterruptedException e) {  
  19.         }  
  20.    
  21.         thread.stop(new Exception("stop")); // note the stack trace  
  22.     }  

從上面的代碼你應(yīng)該可以看出兩件事情:

  1. 使用stop來終止一個線程是不講道理、極其殘暴的,不論目標(biāo)線程在執(zhí)行任何語句,一律強(qiáng)行終止線程,最終將導(dǎo)致一些殘缺的對象和不可預(yù)期的問題產(chǎn)生。
  2. 被終止的線程沒有任何異常拋出,你在線程終止后找不到任何被終止時執(zhí)行的代碼行,或者是堆棧信息(上面代碼打印的異常僅僅是main線程執(zhí)行stop語句的異常而已,并非被終止的線程)。

很難想象這樣的設(shè)計出自一個連指針都被廢掉的類型安全的編程語言,對不對?再來看看suspend的使用,有引起死鎖的隱患:

 
 
 
 
  1. public class Suspend extends Thread {  
  2.     @Override 
  3.     public void run() {  
  4.         synchronized (this) {  
  5.             while (true)  
  6.                 ;  
  7.         }  
  8.     }  
  9.    
  10.     public static void main(String[] args) {  
  11.         Thread thread = new Suspend();  
  12.         thread.start();  
  13.    
  14.         try {  
  15.             sleep(1000);  
  16.         } catch (InterruptedException e) {  
  17.         }  
  18.    
  19.         thread.suspend();  
  20.    
  21.         synchronized (thread) { // dead lock  
  22.             System.out.println("got the lock");  
  23.             thread.resume();  
  24.         }  
  25.     }  

從上面的代碼可以看出,Suspend線程被掛起時,依然占有鎖,而當(dāng)main線程期望去獲取該線程來喚醒它時,徹底癱瘓了。由于suspend在這里是無期限限制的,這會變成一個徹徹底底的死鎖。

相反,看看這三個方法的改進(jìn)品和替代品:wait()、notify()和sleep(),它們令線程之間的交互就友好得多:

 
 
 
 
  1. public class Wait extends Thread {  
  2.     @Override 
  3.     public void run() {  
  4.         System.out.println("start");  
  5.         synchronized (this) { // wait/notify/notifyAll use the same  
  6.                                 // synchronization resource  
  7.             try {  
  8.                 this.wait();  
  9.             } catch (InterruptedException e) {  
  10.                 e.printStackTrace(); // notify won't throw exception  
  11.             }  
  12.         }  
  13.     }  
  14.    
  15.     public static void main(String[] args) {  
  16.         Thread thread = new Wait();  
  17.         thread.start();  
  18.         try {  
  19.             sleep(2000);  
  20.         } catch (InterruptedException e) {  
  21.         }  
  22.         synchronized (thread) {  
  23.             System.out.println("Wait() will release the lock!");  
  24.             thread.notify();  
  25.         }  
  26.     }  

在wait和notify搭配使用的過程中,注意需要把它們鎖定到同一個資源上(例如對象a),即:

  1. 一個線程中synchronized(a),并在同步塊中執(zhí)行a.wait()
  2. 另一個線程中synchronized(a),并在同步塊中執(zhí)行a.notify()

再來看一看sleep方法的使用,回答下面兩個問題:

  1. 和wait比較一下,為什么sleep被設(shè)計為Thread的一個靜態(tài)方法(即只讓當(dāng)前線程sleep)?
  2. 為什么sleep必須要傳入一個時間參數(shù),而不允許不限期地sleep?

如果我前面說的你都理解了,你應(yīng)該能回答這兩個問題。

 
 
 
 
  1. public class Sleep extends Thread {  
  2.     @Override 
  3.     public void run() {  
  4.         System.out.println("start");  
  5.         synchronized (this) { // sleep() can use (or not) any synchronization resource  
  6.             try {  
  7.                 /**  
  8.                  * Do you know: 
     
  9.                  * 1. Why sleep() is designed as a static method comparing with  
  10.                  * wait?
     
  11.                  * 2. Why sleep() must have a timeout parameter?  
  12.                  */ 
  13.                 this.sleep(10000);  
  14.             } catch (InterruptedException e) {  
  15.                 e.printStackTrace(); // notify won't throw exception  
  16.             }  
  17.         }  
  18.     }  
  19.    
  20.    
  21.     public static void main(String[] args) {  
  22.         Thread thread = new Sleep();  
  23.         thread.start();  
  24.         try {  
  25.             sleep(2000);  
  26.         } catch (InterruptedException e) {  
  27.         }  
  28.         synchronized (thread) {  
  29.             System.out.println("Has sleep() released the lock!");  
  30.             thread.notify();  
  31.         }  
  32.     }  

在這個JDK版本中,引入線程變量ThreadLocal這個類:

每一個線程都掛載了一個ThreadLocalMap。ThreadLocal這個類的使用很有意思,get方法沒有key傳入,原因就在于這個 key就是當(dāng)前你使用的這個ThreadLocal它自己。ThreadLocal的對象生命周期可以伴隨著整個線程的生命周期。因此,倘若在線程變量里存放持續(xù)增長的對象(最常見是一個不受良好管理的map),很容易導(dǎo)致內(nèi)存泄露。

 
 
 
 
  1. public class ThreadLocalUsage extends Thread {  
  2.     public User user = new User();  
  3.    
  4.     public User getUser() {  
  5.         return user;  
  6.     }  
  7.    
  8.     @Override 
  9.     public void run() {  
  10.         this.user.set("var1");  
  11.    
  12.         while (true) {  
  13.             try {  
  14.                 sleep(1000);  
  15.             } catch (InterruptedException e) {  
  16.             }  
  17.             System.out.println(this.user.get());  
  18.         }  
  19.     }  
  20.    
  21.     public static void main(String[] args) {  
  22.    
  23.         ThreadLocalUsage thread = new ThreadLocalUsage();  
  24.         thread.start();  
  25.    
  26.         try {  
  27.             sleep(4000);  
  28.         } catch (InterruptedException e) {  
  29.         }  
  30.    
  31.         thread.user.set("var2");  
  32.    
  33.     }  
  34. }  
  35.    
  36. class User {  
  37.    
  38.     private static ThreadLocal enclosure = new ThreadLocal(); // is it must be static?  
  39.    
  40.     public void set(Object object) {  
  41.         enclosure.set(object);  
  42.     }  
  43.    
  44.     public Object get() {  
  45.         return enclosure.get();  
  46.     }  
  47. 上面的例子會一直打印var1,而不會打印var2,就是因為不同線程中的ThreadLocal是互相獨立的。

    用jstack工具可以找到鎖相關(guān)的信息,如果線程占有鎖,但是由于執(zhí)行到wait方法時處于wait狀態(tài)暫時釋放了鎖,會打印waiting on的信息:

     
     
     
     
    1. "Thread-0" prio=6 tid=0x02bc4400 nid=0xef44 in Object.wait() [0x02f0f000]  
    2.    java.lang.Thread.State: WAITING (on object monitor)  
    3.         at java.lang.Object.wait(Native Method)  
    4.         - waiting on <0x22a7c3b8> (a Wait)  
    5.         at java.lang.Object.wait(Object.java:485)  
    6.         at Wait.run(Wait.java:8)  
    7.         - locked <0x22a7c3b8> (a Wait) 

    如果程序持續(xù)占有某個鎖(例如sleep方法在sleep期間不會釋放鎖),會打印locked的信息:

     
     
     
     
    1. "Thread-0" prio=6 tid=0x02baa800 nid=0x1ea4 waiting on condition [0x02f0f000]  
    2.    java.lang.Thread.State: TIMED_WAITING (sleeping)  
    3.         at java.lang.Thread.sleep(Native Method)  
    4.         at Wait.run(Wait.java:8)  
    5.         - locked <0x22a7c398> (a Wait) 

    而如果是線程希望進(jìn)入某同步塊,而在等待鎖的釋放,會打印waiting to的信息:

     
     
     
     
    1. "main" prio=6 tid=0x00847400 nid=0xf984 waiting for monitor entry [0x0092f000]  
    2.    java.lang.Thread.State: BLOCKED (on object monitor)  
    3.         at Wait.main(Wait.java:23)  
    4.         - waiting to lock <0x22a7c398> (a Wait) 

    #p#

    JDK 1.4

    在2002年4月發(fā)布的JDK1.4中,正式引入了NIO。JDK在原有標(biāo)準(zhǔn)IO的基礎(chǔ)上,提供了一組多路復(fù)用IO的解決方案。

    通過在一個Selector上掛接多個Channel,通過統(tǒng)一的輪詢線程檢測,每當(dāng)有數(shù)據(jù)到達(dá),觸發(fā)監(jiān)聽事件,將事件分發(fā)出去,而不是讓每一個channel長期消耗阻塞一個線程等待數(shù)據(jù)流到達(dá)。所以,只有在對資源爭奪劇烈的高并發(fā)場景下,才能見到NIO的明顯優(yōu)勢。

    相較于面向流的傳統(tǒng)方式這種面向塊的訪問方式會丟失一些簡易性和靈活性。下面給出一個NIO接口讀取文件的簡單例子(僅示意用):

     
     
     
     
    1. import java.io.FileInputStream;  
    2. import java.io.IOException;  
    3. import java.nio.ByteBuffer;  
    4. import java.nio.channels.FileChannel;  
    5.    
    6. public class NIO {  
    7.    
    8.     public static void nioRead(String file) throws IOException {  
    9.         FileInputStream in = new FileInputStream(file);  
    10.         FileChannel channel = in.getChannel();  
    11.    
    12.         ByteBuffer buffer = ByteBuffer.allocate(1024);  
    13.         channel.read(buffer);  
    14.         byte[] b = buffer.array();  
    15.         System.out.println(new String(b));  
    16.         channel.close();  
    17.     }  

    JDK 5.0

    2004年9月起JDK 1.5發(fā)布,并正式更名到5.0。有個笑話說,軟件行業(yè)有句話,叫做“不要用3.0版本以下的軟件”,意思是說版本太小的話往往軟件質(zhì)量不過關(guān)——但是按照這種說法,JDK的原有版本命名方式得要到啥時候才有3.0啊,于是1.4以后通過版本命名方式的改變直接升到5.0了。

    JDK 5.0不只是版本號命名方式變更那么簡單,對于多線程編程來說,這里發(fā)生了兩個重大事件,JSR 133和JSR 166的正式發(fā)布。

    JSR 133

    JSR 133重新明確了Java內(nèi)存模型,事實上,在這之前,常見的內(nèi)存模型包括連續(xù)一致性內(nèi)存模型和先行發(fā)生模型。

    對于連續(xù)一致性模型來說,程序執(zhí)行的順序和代碼上顯示的順序是完全一致的。這對于現(xiàn)代多核,并且指令執(zhí)行優(yōu)化的CPU來說,是很難保證的。而且,順序一致性的保證將JVM對代碼的運行期優(yōu)化嚴(yán)重限制住了。

    但是JSR 133指定的先行發(fā)生(Happens-before)使得執(zhí)行指令的順序變得靈活:

    • 在同一個線程里面,按照代碼執(zhí)行的順序(也就是代碼語義的順序),前一個操作先于后面一個操作發(fā)生
    • 對一個monitor對象的解鎖操作先于后續(xù)對同一個monitor對象的鎖操作
    • 對volatile字段的寫操作先于后面的對此字段的讀操作
    • 對線程的start操作(調(diào)用線程對象的start()方法)先于這個線程的其他任何操作
    • 一個線程中所有的操作先于其他任何線程在此線程上調(diào)用 join()方法
    • 如果A操作優(yōu)先于B,B操作優(yōu)先于C,那么A操作優(yōu)先于C

    而在內(nèi)存分配上,將每個線程各自的工作內(nèi)存(甚至包括)從主存中獨立出來,更是給JVM大量的空間來優(yōu)化線程內(nèi)指令的執(zhí)行。主存中的變量可以被拷貝到線程的工作內(nèi)存中去單獨執(zhí)行,在執(zhí)行結(jié)束后,結(jié)果可以在某個時間刷回主存:

    但是,怎樣來保證各個線程之間數(shù)據(jù)的一致性?JLS給的辦法就是,默認(rèn)情況下,不能保證任意時刻的數(shù)據(jù)一致性,但是通過對 synchronized、volatile和final這幾個語義被增強(qiáng)的關(guān)鍵字的使用,可以做到數(shù)據(jù)一致性。要解釋這個問題,不如看一看經(jīng)典的 DCL(Double Check Lock)問題:

     
     
     
     
    1. public class DoubleCheckLock {  
    2.     private volatile static DoubleCheckLock instance; // Do I need add "volatile" here?  
    3.     private final Element element = new Element(); // Should I add "final" here? Is a "final" enough here? Or I should use "volatile"?  
    4.    
    5.     private DoubleCheckLock() {  
    6.     }  
    7.    
    8.     public static DoubleCheckLock getInstance() {  
    9.         if (null == instance)  
    10.             synchronized (DoubleCheckLock.class) {  
    11.                 if (null == instance)  
    12.                     instance = new DoubleCheckLock();  
    13.                     //the writes which initialize instance and the write to the instance field can be reordered without "volatile"  
    14.             }  
    15.    
    16.         return instance;  
    17.     }  
    18.    
    19.     public Element getElement() {  
    20.         return element;  
    21.     }  
    22.    
    23. }  
    24.    
    25. class Element {  
    26.     public String name = new String("abc");  

    在上面這個例子中,如果不對instance聲明的地方使用volatile關(guān)鍵字,JVM將不能保證getInstance方法獲取到的 instance是一個完整的、正確的instance,而volatile關(guān)鍵字保證了instance的可見性,即能夠保證獲取到當(dāng)時真實的 instance對象。

    但是問題沒有那么簡單,對于上例中的element而言,如果沒有volatile和final修飾,element里的name也無法在前文所述的instance返回給外部時
    本文名稱:Java多線程發(fā)展簡史
    地址分享:http://www.5511xx.com/article/djseceg.html